diff --git a/contracts/event/events.go b/contracts/event/events.go index 525136f95..171ed3dbc 100644 --- a/contracts/event/events.go +++ b/contracts/event/events.go @@ -1,12 +1,8 @@ package event -type Instance interface { - // Register event listeners to the application. - Register(map[Event][]Listener) - // Job create a new event task. - Job(event Event, args []Arg) Task - // GetEvents gets all registered events. - GetEvents() map[Event][]Listener +type Arg struct { + Type string + Value any } type Event interface { @@ -14,27 +10,66 @@ type Event interface { Handle(args []Arg) ([]Arg, error) } -type Listener interface { - // Signature returns the unique identifier for the listener. - Signature() string +type EventListener interface { + // Handle the event. + Handle(event any, args ...any) error +} + +type EventQueueListener interface { // Queue configure the event queue options. Queue(args ...any) Queue - // Handle the event. - Handle(args ...any) error + + EventListener + Signature + ShouldQueue } -type Task interface { - // Dispatch an event and call the listeners. - Dispatch() error +type Instance interface { + // Dispatch fires an event and calls the listeners. + Dispatch(event any, payload ...[]Arg) []any + // GetEvents gets all registered events. + GetEvents() map[Event][]Listener + // Job create a new event task. + // Deprecated: Use Dispatch instead. Job will be removed in a future version. + Job(event Event, args []Arg) Task + // Listen registers an event listener with the dispatcher. + // events can be: string, []string, Event, []Event, or any other type + // listener can be: function, class, or any callable + Listen(events any, listener ...any) error + // Register event listeners to the application. + Register(map[Event][]Listener) } -type Arg struct { - Value any - Type string +type Listener interface { + // Handle the event. + Handle(args ...any) error + // Queue configure the event queue options. + Queue(args ...any) Queue + // Signature returns the unique identifier for the listener. + Signature() string } type Queue struct { Connection string - Queue string Enable bool + Queue string +} + +type QueueListener interface { + Listener + Signature + ShouldQueue +} + +type ShouldQueue interface { + ShouldQueue() bool +} + +type Signature interface { + Signature() string +} + +type Task interface { + // Dispatch an event and call the listeners. + Dispatch() error } diff --git a/event/application.go b/event/application.go index 24c2b76af..ec44da180 100644 --- a/event/application.go +++ b/event/application.go @@ -2,22 +2,58 @@ package event import ( "slices" + "sync" "github.com/goravel/framework/contracts/event" "github.com/goravel/framework/contracts/queue" ) type Application struct { - events map[event.Event][]event.Listener - queue queue.Queue + listeners map[string][]any + mu sync.RWMutex + wildcards map[string][]any + wildcardsCache sync.Map + events map[event.Event][]event.Listener + queue queue.Queue } func NewApplication(queue queue.Queue) *Application { return &Application{ - queue: queue, + listeners: make(map[string][]any), + mu: sync.RWMutex{}, + wildcards: make(map[string][]any), + wildcardsCache: sync.Map{}, + queue: queue, } } +// GetEvents returns all registered events and their listeners. +func (app *Application) GetEvents() map[event.Event][]event.Listener { + return app.events +} + +// Job returns a new Task for the given event and arguments. +// Deprecated: Use Dispatch instead. Job will be removed in a future version. +// The new Dispatch method fires events immediately and returns listener responses. +// +// Migration example: +// +// // Old way +// task := app.Job(event, args) +// task.Dispatch() +// +// // New way +// responses := app.Dispatch(event, args) +func (app *Application) Job(e event.Event, args []event.Arg) event.Task { + listeners, ok := app.events[e] + if !ok { + listeners = make([]event.Listener, 0) + } + + return NewTask(app.queue, args, e, listeners) +} + +// Register registers events and their listeners. func (app *Application) Register(events map[event.Event][]event.Listener) { var ( jobs []queue.Job @@ -40,16 +76,3 @@ func (app *Application) Register(events map[event.Event][]event.Listener) { app.queue.Register(jobs) } - -func (app *Application) GetEvents() map[event.Event][]event.Listener { - return app.events -} - -func (app *Application) Job(e event.Event, args []event.Arg) event.Task { - listeners, ok := app.events[e] - if !ok { - listeners = make([]event.Listener, 0) - } - - return NewTask(app.queue, args, e, listeners) -} diff --git a/event/application_dispatch.go b/event/application_dispatch.go new file mode 100644 index 000000000..c4df67607 --- /dev/null +++ b/event/application_dispatch.go @@ -0,0 +1,328 @@ +package event + +import ( + "reflect" + "time" + + "github.com/goravel/framework/contracts/event" + "github.com/goravel/framework/support/str" +) + +// Dispatch fires an event and calls all registered listeners for that event. +// It supports both direct event names and wildcard patterns, and returns all listener responses. +// This method is thread-safe and can be called concurrently. +// +// Parameters: +// - evt: Can be a string, event.Event interface, or any other type +// - payload: Optional arguments to pass to event listeners +// +// Returns: A slice containing all non-nil responses from event listeners +// +// Example: +// +// // Dispatch a string event +// responses := app.Dispatch("user.created", []event.Arg{{Value: user, Type: "User"}}) +// +// // Dispatch a struct event +// type UserCreated struct { User User } +// responses := app.Dispatch(&UserCreated{User: user}) +// +// // Dispatch without payload +// responses := app.Dispatch("app.started") +func (app *Application) Dispatch(evt any, payload ...[]event.Arg) []any { + app.mu.RLock() + defer app.mu.RUnlock() + + // as payload is optional, we need to check if it is empty + var args []event.Arg + if len(payload) > 0 { + args = payload[0] + } + + return app.invokeListeners(evt, args, false) +} + +// call invokes a function with the given arguments safely, recovering from panics. +// If a panic occurs, it returns nil. This allows event processing to continue +// even if a single listener panics. +// +// Parameters: +// - fn: The function to invoke +// - args: The arguments to pass to the function +// +// Returns: The first return value from the function, or nil if function panics or returns nothing +func (app *Application) call(fn reflect.Value, args []reflect.Value) any { + defer func() { + // Recover from panics to prevent one bad listener from breaking + // the entire event dispatch chain + _ = recover() + }() + if results := fn.Call(args); len(results) > 0 { + return results[0].Interface() + } + return nil +} + +// callListener invokes a single listener for the given event. +// It handles three types of listeners: +// - QueueListener: Queued execution if ShouldQueue() returns true +// - EventQueueListener: Queued execution for event-aware listeners +// - Regular listeners: Direct synchronous execution +// +// The method uses reflection to dynamically match listener parameters with +// the event and payload arguments. +// +// Parameters: +// - listener: The listener to invoke (function, struct with Handle method, or queue listener) +// - evt: The event being dispatched +// - args: Optional payload arguments +// +// Returns: The listener's return value, or nil if no value returned +func (app *Application) callListener(listener any, evt any, args []event.Arg) any { + // Handle queueing + if ql, ok := listener.(event.QueueListener); ok && ql.ShouldQueue() { + return app.queueListener(ql, args) + } + if eql, ok := listener.(event.EventQueueListener); ok && eql.ShouldQueue() { + return app.queueEventListener(eql, evt, args) // Direct queueing without wrapper + } + + // Get the callable (function or method) + v := reflect.ValueOf(listener) + if v.Kind() != reflect.Func { + if method := v.MethodByName("Handle"); method.IsValid() { + v = method + } else { + return nil + } + } + + // Call with dynamic args + return app.callListenerHandle(v, evt, args) +} + +// callListenerHandle invokes a listener function with dynamically matched arguments. +// It builds the argument list by matching the event and payload to function parameters. +// Supports variadic parameters and automatic type conversion. +// +// Parameters: +// - fn: The function to invoke +// - evt: The event being dispatched +// - args: Optional payload arguments +// +// Returns: The function's return value, or nil if no value returned +func (app *Application) callListenerHandle(fn reflect.Value, evt any, args []event.Arg) any { + t := fn.Type() + numIn := t.NumIn() + + if numIn == 0 { + return app.call(fn, nil) + } + + // Build args in one pass + callArgs := make([]reflect.Value, numIn) + payloadIdx := 0 + + for i := 0; i < numIn; i++ { + param := t.In(i) + + // First param gets event if it fits + if i == 0 && app.canUse(evt, param) { + callArgs[i] = app.convert(evt, param) + continue + } + + // Use payload args + if payloadIdx < len(args) && app.canUse(args[payloadIdx].Value, param) { + callArgs[i] = app.convert(args[payloadIdx].Value, param) + payloadIdx++ + continue + } + + // Variadic slice (consumes all remaining) + if param.Kind() == reflect.Slice && param.Elem().Kind() == reflect.Interface { + remaining := make([]any, len(args)-payloadIdx) + for j, arg := range args[payloadIdx:] { + remaining[j] = arg.Value + } + callArgs[i] = reflect.ValueOf(remaining) + break + } + + // Zero value + callArgs[i] = reflect.Zero(param) + } + + return app.call(fn, callArgs) +} + +// canUse checks if a value can be used for a given parameter type. +// Returns true if the value is nil, assignable to target, or target is string. +func (app *Application) canUse(val any, target reflect.Type) bool { + if val == nil { + return true + } + return reflect.TypeOf(val).AssignableTo(target) || target.Kind() == reflect.String +} + +// convert converts a value to the target reflect type. +// Handles nil values, string conversions, and type assignments. +func (app *Application) convert(val any, target reflect.Type) reflect.Value { + if val == nil { + return reflect.Zero(target) + } + if target.Kind() == reflect.String { + return reflect.ValueOf(app.getEventName(val)) + } + v := reflect.ValueOf(val) + if v.Type().AssignableTo(target) { + return v + } + return reflect.Zero(target) +} + +// getWildcardListeners returns all wildcard listeners that match the given event name. +// It iterates through registered wildcard patterns (e.g., "user.*") and returns listeners +// for patterns that match the event name. +// +// The results are cached in wildcardsCache for performance. The cache is automatically +// cleared when new wildcard listeners are registered. +// +// Parameters: +// - eventName: The event name to match against wildcard patterns +// +// Returns: A slice of listeners from all matching wildcard patterns +func (app *Application) getWildcardListeners(eventName any) []any { + var wildcardListeners []any + eventNameStr := app.getEventName(eventName) + + for event, wildcard := range app.wildcards { + if str.Of(eventNameStr).Is(app.getEventName(event)) { + wildcardListeners = append(wildcardListeners, wildcard...) + } + } + + // Cache results for performance - thread-safe with sync.Map + app.wildcardsCache.Store(eventNameStr, wildcardListeners) + + return wildcardListeners +} + +// invokeListeners calls all registered listeners for an event. +// It collects responses from all listeners and can optionally halt on first response. +// +// Parameters: +// - evt: The event being dispatched +// - payload: Optional payload arguments +// - halt: If true, stops after first non-nil response +// +// Returns: Slice of all non-nil listener responses +func (app *Application) invokeListeners(evt any, payload []event.Arg, halt bool) []any { + var responses []any + listeners := app.prepareListeners(app.getEventName(evt)) + for _, listener := range listeners { + response := app.callListener(listener, evt, payload) + if response != nil { + responses = append(responses, response) + if halt { + break + } + } + } + + return responses +} + +// prepareListeners gathers all listeners for a given event. +// Combines direct listeners and wildcard listeners, using cache when available. +// +// Parameters: +// - event: The event name to get listeners for +// +// Returns: Combined slice of all matching listeners +func (app *Application) prepareListeners(event string) []any { + var allListeners []any + + // Add direct listeners + if listeners, exists := app.listeners[event]; exists { + allListeners = append(allListeners, listeners...) + } + + // Add wildcard listeners (use cache if available) + if cached, exists := app.wildcardsCache.Load(event); exists { + if listeners, ok := cached.([]any); ok { + allListeners = append(allListeners, listeners...) + } + } else { + // event is already a string, no need to call getEventName again + wildcardListeners := app.getWildcardListeners(event) + allListeners = append(allListeners, wildcardListeners...) + } + + return allListeners +} + +// queueEventListener creates a dynamic queue job for an event-aware listener. +// Wraps the listener in a closure that preserves the event context. +// +// Parameters: +// - listener: The event queue listener to wrap +// - evt: The event being dispatched +// - args: Optional payload arguments +// +// Returns: nil (queuing is asynchronous) +func (app *Application) queueEventListener(listener event.EventQueueListener, evt any, args []event.Arg) any { + // Capture event in local scope to avoid closure variable capture bug + eventCopy := evt + + // Create a dynamic queue job using closure + job := &dynamicQueueJob{ + signature: listener.Signature(), + handler: func(queueArgs ...any) error { + // Convert queue args back to event args format + eventArgs := make([]any, len(queueArgs)) + copy(eventArgs, queueArgs) + return listener.Handle(eventCopy, eventArgs...) + }, + shouldQueue: listener.ShouldQueue(), + queueConfig: listener, // For queue configuration methods + } + + return app.queueListener(job, args) +} + +// queueListener dispatches a listener to the queue system. +// Configures the queue task based on listener configuration methods. +// +// Parameters: +// - listener: The queue listener to dispatch +// - payload: Optional payload arguments +// +// Returns: nil (queuing is asynchronous) +func (app *Application) queueListener(listener event.QueueListener, payload []event.Arg) any { + task := app.queue.Job(listener, eventArgsToQueueArgs(payload)) + + // Configure queue task using interface assertions instead of reflection + // This is significantly faster than MethodByName approach + + // Configure connection if listener implements ViaConnection + if configured, ok := listener.(interface{ ViaConnection() string }); ok { + task.OnConnection(configured.ViaConnection()) + } + + // Configure queue if listener implements ViaQueue + if configured, ok := listener.(interface{ ViaQueue() string }); ok { + task.OnQueue(configured.ViaQueue()) + } + + // Configure delay if listener implements WithDelay + if configured, ok := listener.(interface{ WithDelay() int64 }); ok { + task.Delay(time.Now().Add(time.Duration(configured.WithDelay()) * time.Second)) + } + + // Dispatch task - errors are intentionally not returned as per framework pattern + // Queued jobs handle their own error recovery and reporting + _ = task.Dispatch() + + return nil +} diff --git a/event/application_listen.go b/event/application_listen.go new file mode 100644 index 000000000..2a092d56c --- /dev/null +++ b/event/application_listen.go @@ -0,0 +1,131 @@ +package event + +import ( + "reflect" + "strings" + + "github.com/goravel/framework/contracts/event" + "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" +) + +// Listen registers event listeners for one or more events. +// It supports various event types and listener formats: +// - Events: string, []string, event.Event, []event.Event, or any custom type +// - Listeners: functions, structs implementing listener interfaces, or closures +// +// The method automatically handles queue registration for listeners that implement ShouldQueue. +// Wildcard patterns (containing "*") are supported for flexible event matching. +// +// Examples: +// +// // Listen to a single string event +// app.Listen("user.created", func(args ...any) error { +// return nil +// }) +// +// // Listen to multiple events +// app.Listen([]string{"user.created", "user.updated"}, listener) +// +// // Listen to wildcard events +// app.Listen("user.*", func(args ...any) error { +// return nil +// }) +// +// // Listen with event structs +// app.Listen(UserCreatedEvent{}, &UserCreatedListener{}) +func (app *Application) Listen(events any, listener ...any) error { + app.mu.Lock() + defer app.mu.Unlock() + + if len(listener) == 0 { + // it may be a closure + if fn := reflect.ValueOf(events); fn.Kind() == reflect.Func { + return app.handleClosure(fn) + } + + return errors.New("listener is required") + } + + switch e := events.(type) { + case string: + app.setupEvents(e, listener[0]) + case []string: + for _, eventName := range e { + app.setupEvents(eventName, listener[0]) + } + case []event.Event: + for _, evt := range e { + app.setupEvents(evt, listener[0]) + } + case event.Event: + app.setupEvents(e, listener[0]) + default: + if eventName := app.getEventName(events); eventName != "" { + app.setupEvents(eventName, listener[0]) + } else { + return errors.New("invalid event type") + } + } + + return nil +} + +func (app *Application) handleClosure(fn reflect.Value) error { + fnType := fn.Type() + + if fnType.NumIn() != 1 || !app.isEventType(fnType.In(0)) { + return errors.New("closure must accept exactly one event parameter") + } + + // get first parameter and pass to setupEvents + ptr := reflect.New(fnType.In(0).Elem()).Interface() + app.setupEvents(ptr, fn) + + return nil +} + +func (app *Application) isEventType(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Implements(reflect.TypeOf((*event.Event)(nil)).Elem()) +} + +func (app *Application) setupEvents(e any, listener any) { + eventName := app.getEventName(e) + + // Register queued listeners with the queue system + if l, ok := listener.(event.EventQueueListener); ok { + // Capture event in local scope to avoid closure variable capture bug + eventCopy := e + app.queue.Register([]queue.Job{ + &dynamicQueueJob{ + signature: l.Signature(), + handler: func(queueArgs ...any) error { + // Convert queue args back to event args format + eventArgs := make([]any, len(queueArgs)) + copy(eventArgs, queueArgs) + return l.Handle(eventCopy, eventArgs...) + }, + shouldQueue: l.ShouldQueue(), + queueConfig: l, + }, + }) + } else if l, ok := listener.(event.QueueListener); ok { + app.queue.Register([]queue.Job{l}) + } + + // Register the listener based on event type + if strings.Contains(eventName, "*") { + app.setupWildcardListen(eventName, listener) + } else { + app.listeners[eventName] = append(app.listeners[eventName], listener) + } +} + +func (app *Application) setupWildcardListen(eventName string, listener any) { + app.wildcards[eventName] = append(app.wildcards[eventName], listener) + // Clear cache to ensure fresh wildcard matching + app.wildcardsCache.Range(func(key, value any) bool { + app.wildcardsCache.Delete(key) + return true + }) +} diff --git a/event/application_test.go b/event/application_test.go index 0bdd68e2a..1765b7042 100644 --- a/event/application_test.go +++ b/event/application_test.go @@ -1,10 +1,13 @@ package event import ( + "reflect" + "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/goravel/framework/contracts/event" "github.com/goravel/framework/contracts/queue" @@ -74,3 +77,597 @@ func TestApplication_Register(t *testing.T) { }) } } + +func TestApplication_Dispatch(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + // Test dispatching a string event + t.Run("DispatchStringEvent", func(t *testing.T) { + eventName := "test.event" + app.listeners = map[string][]any{ + eventName: { + func(name string) any { + assert.Equal(t, eventName, name) + return "response" + }, + }, + } + + responses := app.Dispatch(eventName) + require.Len(t, responses, 1) + assert.Equal(t, "response", responses[0]) + }) + + // Test dispatching an event with payload + t.Run("DispatchWithPayload", func(t *testing.T) { + eventName := "user.created" + payload := []event.Arg{{Value: "testuser", Type: "string"}} + + app.listeners = map[string][]any{ + eventName: { + func(name string, username any) any { + assert.Equal(t, eventName, name) + assert.Equal(t, "testuser", username) + return "processed" + }, + }, + } + + responses := app.Dispatch(eventName, payload) + require.Len(t, responses, 1) + assert.Equal(t, "processed", responses[0]) + }) + + // Test dispatching to wildcard listeners + t.Run("DispatchWithWildcardListeners", func(t *testing.T) { + eventName := "user.registered" + app.listeners = map[string][]any{} + app.wildcards = map[string][]any{ + "user.*": { + func(name string) any { + assert.Equal(t, eventName, name) + return "wildcard" + }, + }, + } + app.wildcardsCache = sync.Map{} + + responses := app.Dispatch(eventName) + require.Len(t, responses, 1) + assert.Equal(t, "wildcard", responses[0]) + }) + + // Test dispatching a struct event + t.Run("DispatchStructEvent", func(t *testing.T) { + evt := &TestEventCustom{} + app.listeners = map[string][]any{ + "TestEventCustom": { + func(e *TestEventCustom) any { + assert.Equal(t, evt, e) + return "received" + }, + }, + } + + responses := app.Dispatch(evt) + require.Len(t, responses, 1) + assert.Equal(t, "received", responses[0]) + }) +} + +func TestApplication_Listen(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + // Test listening to a string event + t.Run("ListenStringEvent", func(t *testing.T) { + eventName := "test.event" + handler := func() error { return nil } + + err := app.Listen(eventName, handler) + require.NoError(t, err) + + listeners, exists := app.listeners[eventName] + require.True(t, exists) + require.Len(t, listeners, 1) + }) + + // Test listening to multiple string events + t.Run("ListenMultipleEvents", func(t *testing.T) { + events := []string{"event1", "event2"} + handler := func() error { return nil } + + err := app.Listen(events, handler) + require.NoError(t, err) + + for _, e := range events { + listeners, exists := app.listeners[e] + require.True(t, exists) + require.Len(t, listeners, 1) + } + }) + + // Test listening to a wildcard event + t.Run("ListenWildcardEvent", func(t *testing.T) { + eventName := "user.*" + handler := func() error { return nil } + + err := app.Listen(eventName, handler) + require.NoError(t, err) + + listeners, exists := app.wildcards[eventName] + require.True(t, exists) + require.Len(t, listeners, 1) + + // Verify cache is empty by checking it has no entries + cacheEmpty := true + app.wildcardsCache.Range(func(key, value any) bool { + cacheEmpty = false + return false // Stop iterating + }) + assert.True(t, cacheEmpty, "Cache should be cleared") + }) + + // Test listening to an event without a listener + t.Run("ListenNoListener", func(t *testing.T) { + err := app.Listen("event.name") + require.Error(t, err) + assert.Contains(t, err.Error(), "listener is required") + }) + + // Test listening with closure - this should fail since closure doesn't match expected pattern + t.Run("ListenClosure", func(t *testing.T) { + closure := func(evt *TestEventCustom) error { + return nil + } + + err := app.Listen(closure) + require.Error(t, err) + assert.Contains(t, err.Error(), "closure must accept exactly one event parameter") + }) + + // Test listening with invalid closure + t.Run("ListenInvalidClosure", func(t *testing.T) { + invalidClosure := func(a, b string) error { + return nil + } + + err := app.Listen(invalidClosure) + require.Error(t, err) + assert.Contains(t, err.Error(), "closure must accept exactly one event parameter") + }) + + // Test listening to event interface + t.Run("ListenEventInterface", func(t *testing.T) { + // Create fresh app for this test + mockQueueLocal := mocksqueue.NewQueue(t) + appLocal := NewApplication(mockQueueLocal) + + evt := &TestEventCustom{} + handler := func() error { return nil } + + err := appLocal.Listen(evt, handler) + require.NoError(t, err) + + listeners, exists := appLocal.listeners["TestEventCustom"] + require.True(t, exists) + require.Len(t, listeners, 1) + }) + + // Test listening to slice of events + t.Run("ListenEventSlice", func(t *testing.T) { + // Create fresh app for this test to avoid interference + mockQueueLocal := mocksqueue.NewQueue(t) + appLocal := NewApplication(mockQueueLocal) + + events := []event.Event{&TestEventCustom{}, &TestEvent{}} + handler := func() error { return nil } + + err := appLocal.Listen(events, handler) + require.NoError(t, err) + + // Check both events were registered + listeners1, exists1 := appLocal.listeners["TestEventCustom"] + require.True(t, exists1) + require.Len(t, listeners1, 1) + + listeners2, exists2 := appLocal.listeners["TestEvent"] + require.True(t, exists2) + require.Len(t, listeners2, 1) + }) + + // Test listening with valid event type that has getEventName + t.Run("ListenValidEventType", func(t *testing.T) { + type ValidEvent struct{ Name string } + validEvent := ValidEvent{Name: "valid"} + handler := func() error { return nil } + + err := app.Listen(validEvent, handler) + require.NoError(t, err) + + // Should register under the struct name + listeners, exists := app.listeners["ValidEvent"] + require.True(t, exists) + require.Len(t, listeners, 1) + }) +} + +func TestApplication_Job(t *testing.T) { + // Setup test data + mockEvent := mocksevent.NewEvent(t) + mockListener := mocksevent.NewListener(t) + args := []event.Arg{{Value: "test", Type: "string"}} + + // Test with registered event + t.Run("RegisteredEvent", func(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + app.events = map[event.Event][]event.Listener{ + mockEvent: {mockListener}, + } + + // We only test that Job returns a non-nil task + // The actual dispatching happens in Task.Dispatch which is tested separately + task := app.Job(mockEvent, args) + require.NotNil(t, task) + + // Verify the task has the correct properties + taskImpl, ok := task.(*Task) + require.True(t, ok, "Task should be of type *Task") + assert.Equal(t, mockEvent, taskImpl.event) + assert.Equal(t, mockQueue, taskImpl.queue) + assert.Equal(t, args, taskImpl.args) + assert.Equal(t, []event.Listener{mockListener}, taskImpl.listeners) + }) + + // Test with unregistered event + t.Run("UnregisteredEvent", func(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + unregisteredEvent := mocksevent.NewEvent(t) + + // We only test that Job returns a non-nil task with empty listeners + task := app.Job(unregisteredEvent, args) + require.NotNil(t, task) + + // Verify the task has the correct properties + taskImpl, ok := task.(*Task) + require.True(t, ok, "Task should be of type *Task") + assert.Equal(t, unregisteredEvent, taskImpl.event) + assert.Equal(t, mockQueue, taskImpl.queue) + assert.Equal(t, args, taskImpl.args) + assert.Empty(t, taskImpl.listeners) + }) +} + +// Test event with custom signature +type TestEventCustom struct{} + +func (t *TestEventCustom) Signature() string { + return "TestEventCustom" +} + +func (t *TestEventCustom) Handle(args []event.Arg) ([]event.Arg, error) { + return args, nil +} + +// Test queue listener +type TestQueueListener struct { + HandleFunc func(args ...any) error + ShouldQueueFunc bool +} + +func (l *TestQueueListener) Handle(args ...any) error { + if l.HandleFunc != nil { + return l.HandleFunc(args...) + } + return nil +} + +func (l *TestQueueListener) ShouldQueue() bool { + return l.ShouldQueueFunc +} + +func (l *TestQueueListener) Signature() string { + return "TestQueueListener" +} + +// Test event queue listener +type TestEventQueueListener struct { + HandleFunc func(evt any, args ...any) error + ShouldQueueFunc bool +} + +func (l *TestEventQueueListener) Handle(evt any, args ...any) error { + if l.HandleFunc != nil { + return l.HandleFunc(evt, args...) + } + return nil +} + +func (l *TestEventQueueListener) ShouldQueue() bool { + return l.ShouldQueueFunc +} + +func (l *TestEventQueueListener) Signature() string { + return "TestEventQueueListener" +} + +func TestApplication_callListener(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + t.Run("FunctionListener", func(t *testing.T) { + called := false + listener := func(name string) any { + called = true + assert.Equal(t, "test", name) + return "result" + } + + result := app.callListener(listener, "test", nil) + assert.True(t, called) + assert.Equal(t, "result", result) + }) + + t.Run("StructWithHandleMethod", func(t *testing.T) { + listener := &TestListener{} + + result := app.callListener(listener, "test", nil) + assert.Nil(t, result) + }) + + t.Run("QueueListenerShouldNotQueue", func(t *testing.T) { + listener := &TestQueueListener{ + ShouldQueueFunc: false, + } + + // Should call the listener directly when not queued + result := app.callListener(listener, "test", nil) + assert.Nil(t, result) + }) + + t.Run("EventQueueListenerShouldNotQueue", func(t *testing.T) { + listener := &TestEventQueueListener{ + ShouldQueueFunc: false, + } + + // Should call the listener directly when not queued + result := app.callListener(listener, "test", nil) + assert.Nil(t, result) + }) + + t.Run("InvalidListener", func(t *testing.T) { + type InvalidListener struct{} + listener := &InvalidListener{} + + result := app.callListener(listener, "test", nil) + assert.Nil(t, result) + }) +} + +func TestApplication_callListenerHandle(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + t.Run("NoArguments", func(t *testing.T) { + called := false + fn := reflect.ValueOf(func() any { + called = true + return "no-args" + }) + + result := app.callListenerHandle(fn, "test", nil) + assert.True(t, called) + assert.Equal(t, "no-args", result) + }) + + t.Run("EventAsFirstArgument", func(t *testing.T) { + called := false + fn := reflect.ValueOf(func(name string) any { + called = true + assert.Equal(t, "test", name) + return "with-event" + }) + + result := app.callListenerHandle(fn, "test", nil) + assert.True(t, called) + assert.Equal(t, "with-event", result) + }) + + t.Run("WithPayloadArgs", func(t *testing.T) { + called := false + args := []event.Arg{{Value: "payload1", Type: "string"}, {Value: 42, Type: "int"}} + fn := reflect.ValueOf(func(name string, payload string, num int) any { + called = true + assert.Equal(t, "test", name) + assert.Equal(t, "payload1", payload) + assert.Equal(t, 42, num) + return "with-payload" + }) + + result := app.callListenerHandle(fn, "test", args) + assert.True(t, called) + assert.Equal(t, "with-payload", result) + }) + + t.Run("VariadicSliceArgument", func(t *testing.T) { + called := false + args := []event.Arg{{Value: "arg1", Type: "string"}, {Value: "arg2", Type: "string"}} + fn := reflect.ValueOf(func(name string, remaining []any) any { + called = true + assert.Equal(t, "test", name) + assert.Len(t, remaining, 2) + assert.Equal(t, "arg1", remaining[0]) + assert.Equal(t, "arg2", remaining[1]) + return "variadic" + }) + + result := app.callListenerHandle(fn, "test", args) + assert.True(t, called) + assert.Equal(t, "variadic", result) + }) +} + +func TestApplication_getWildcardListeners(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + t.Run("MatchingWildcard", func(t *testing.T) { + listener1 := func() {} + listener2 := func() {} + + app.wildcards = map[string][]any{ + "user.*": {listener1, listener2}, + "order.*": {func() {}}, + } + app.wildcardsCache = sync.Map{} + + listeners := app.getWildcardListeners("user.created") + assert.Len(t, listeners, 2) + + // Check cache was updated + cached, exists := app.wildcardsCache.Load("user.created") + assert.True(t, exists) + if cachedListeners, ok := cached.([]any); ok { + assert.Len(t, cachedListeners, 2) + } + }) + + t.Run("NoMatchingWildcard", func(t *testing.T) { + app.wildcards = map[string][]any{ + "order.*": {func() {}}, + } + app.wildcardsCache = sync.Map{} + + listeners := app.getWildcardListeners("user.created") + assert.Empty(t, listeners) + }) +} + +func TestApplication_prepareListeners(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + t.Run("DirectAndWildcardListeners", func(t *testing.T) { + directListener := func() {} + wildcardListener := func() {} + + app.listeners = map[string][]any{ + "user.created": {directListener}, + } + app.wildcardsCache = sync.Map{} + app.wildcardsCache.Store("user.created", []any{wildcardListener}) + + listeners := app.prepareListeners("user.created") + assert.Len(t, listeners, 2) + }) + + t.Run("OnlyDirectListeners", func(t *testing.T) { + listener := func() {} + + app.listeners = map[string][]any{ + "user.created": {listener}, + } + app.wildcardsCache = sync.Map{} + app.wildcards = make(map[string][]any) + + listeners := app.prepareListeners("user.created") + assert.Len(t, listeners, 1) + }) + + t.Run("NoListeners", func(t *testing.T) { + app.listeners = make(map[string][]any) + app.wildcardsCache = sync.Map{} + app.wildcards = make(map[string][]any) + + listeners := app.prepareListeners("nonexistent.event") + assert.Empty(t, listeners) + }) +} + +func TestApplication_DispatchConcurrent(t *testing.T) { + // This test verifies that concurrent dispatches don't cause data races + // Run with: go test -race to verify + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + eventName := "concurrent.event" + callCount := 0 + var mu sync.Mutex + + app.listeners = map[string][]any{ + eventName: { + func(name string) any { + mu.Lock() + callCount++ + mu.Unlock() + return "response" + }, + }, + } + app.wildcards = map[string][]any{ + "concurrent.*": { + func(name string) any { + mu.Lock() + callCount++ + mu.Unlock() + return "wildcard" + }, + }, + } + + // Dispatch the same event concurrently 100 times + const numGoroutines = 100 + var wg sync.WaitGroup + wg.Add(numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func() { + defer wg.Done() + responses := app.Dispatch(eventName) + // Should get both direct and wildcard listener responses + assert.Len(t, responses, 2) + }() + } + + wg.Wait() + + // Verify all dispatches were processed + mu.Lock() + assert.Equal(t, numGoroutines*2, callCount) // 2 listeners per dispatch + mu.Unlock() +} + +func TestApplication_ErrorRecovery(t *testing.T) { + mockQueue := mocksqueue.NewQueue(t) + app := NewApplication(mockQueue) + + eventName := "test.panic" + successCalled := false + + app.listeners = map[string][]any{ + eventName: { + // First listener panics + func(name string) any { + panic("intentional panic") + }, + // Second listener should still be called + func(name string) any { + successCalled = true + return "success" + }, + }, + } + + // Dispatch should not panic and should continue processing + responses := app.Dispatch(eventName) + + // Second listener should have been called despite first one panicking + assert.True(t, successCalled, "Second listener should be called even if first panics") + + // Only the successful listener returns a response + assert.Len(t, responses, 1) + assert.Equal(t, "success", responses[0]) +} diff --git a/event/task.go b/event/task.go index fcb3a59b5..42521ec37 100644 --- a/event/task.go +++ b/event/task.go @@ -61,6 +61,8 @@ func (receiver *Task) Dispatch() error { return nil } +// eventArgsToQueueArgs converts event arguments to queue arguments. +// This is a simple type conversion that maintains the Type and Value fields. func eventArgsToQueueArgs(args []event.Arg) []contractsqueue.Arg { var queueArgs []contractsqueue.Arg for _, arg := range args { diff --git a/event/utils.go b/event/utils.go new file mode 100644 index 000000000..e6c3846ee --- /dev/null +++ b/event/utils.go @@ -0,0 +1,78 @@ +package event + +import ( + "reflect" + + "github.com/goravel/framework/contracts/event" +) + +// dynamicQueueJob - Minimal queue job implementation using closures +type dynamicQueueJob struct { + signature string + handler func(...any) error + shouldQueue bool + queueConfig any // Original listener for configuration +} + +// Handle processes the job with the given arguments +func (j *dynamicQueueJob) Handle(args ...any) error { + return j.handler(args...) +} + +// Queue method using reflection to call original listener's Queue method +func (j *dynamicQueueJob) Queue(args ...any) event.Queue { + if queueable, ok := j.queueConfig.(interface{ Queue(...any) event.Queue }); ok { + return queueable.Queue(args...) + } + return event.Queue{} +} + +// ShouldQueue returns whether this job should be queued +func (j *dynamicQueueJob) ShouldQueue() bool { + return j.shouldQueue +} + +// Signature returns the unique identifier for this job +func (j *dynamicQueueJob) Signature() string { + return j.signature +} + +// getEventName returns the name of the event. +// examples: +// +// getEventName("user.created") // "user.created" +// getEventName(&UserCreated{}) // "UserCreated" +func (app *Application) getEventName(evt any) string { + switch e := evt.(type) { + case string: + return e + case event.Event: + // Check if event has custom signature method + if signature, ok := e.(event.Signature); ok { + return signature.Signature() + } + + // Use reflection to get struct type name + eventType := reflect.TypeOf(e) + if eventType.Kind() == reflect.Ptr { + eventType = eventType.Elem() + } + + return eventType.Name() + default: + // Use reflection for any other type + eventType := reflect.TypeOf(evt) + if eventType != nil { + if eventType.Kind() == reflect.Ptr { + eventType = eventType.Elem() + } + // For unnamed structs, use the full type string + name := eventType.Name() + if name == "" { + name = eventType.String() + } + return name + } + return "" + } +} diff --git a/event/utils_test.go b/event/utils_test.go new file mode 100644 index 000000000..12490eaa4 --- /dev/null +++ b/event/utils_test.go @@ -0,0 +1,233 @@ +package event + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/goravel/framework/contracts/event" +) + +// TestEventWithSignature is a test event that implements Signature interface +type TestEventWithSignature struct{} + +func (t *TestEventWithSignature) Signature() string { + return "custom.signature" +} + +func (t *TestEventWithSignature) Handle(args []event.Arg) ([]event.Arg, error) { + return args, nil +} + +func Test_dynamicQueueJob_Handle(t *testing.T) { + var handlerCalled bool + job := &dynamicQueueJob{ + signature: "test.job", + handler: func(args ...any) error { + handlerCalled = true + return nil + }, + shouldQueue: true, + } + + err := job.Handle("test") + require.NoError(t, err) + assert.True(t, handlerCalled) +} + +func Test_dynamicQueueJob_ShouldQueue(t *testing.T) { + job1 := &dynamicQueueJob{shouldQueue: true} + assert.True(t, job1.ShouldQueue()) + + job2 := &dynamicQueueJob{shouldQueue: false} + assert.False(t, job2.ShouldQueue()) +} + +func Test_dynamicQueueJob_Signature(t *testing.T) { + job := &dynamicQueueJob{signature: "test.signature"} + assert.Equal(t, "test.signature", job.Signature()) +} + +func Test_dynamicQueueJob_Queue(t *testing.T) { + // Test with queueable config + queueable := &TestListener{} + job1 := &dynamicQueueJob{queueConfig: queueable} + queue := job1.Queue() + assert.False(t, queue.Enable) + + // Test with non-queueable config + job2 := &dynamicQueueJob{queueConfig: "not-queueable"} + emptyQueue := job2.Queue() + assert.Equal(t, event.Queue{}, emptyQueue) +} + +func TestApplication_getEventName(t *testing.T) { + app := &Application{} + + tests := []struct { + name string + event any + expected string + }{ + { + name: "String event", + event: "user.created", + expected: "user.created", + }, + { + name: "Struct event", + event: TestEvent{}, + expected: "TestEvent", + }, + { + name: "Pointer to struct event", + event: &TestEvent{}, + expected: "TestEvent", + }, + { + name: "Event with custom signature", + event: &TestEventWithSignature{}, + expected: "custom.signature", + }, + { + name: "Nil event", + event: nil, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := app.getEventName(tt.event) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestApplication_canUse(t *testing.T) { + app := &Application{} + + tests := []struct { + name string + val any + target reflect.Type + want bool + }{ + { + name: "Nil value", + val: nil, + target: reflect.TypeOf(""), + want: true, + }, + { + name: "Assignable string", + val: "test", + target: reflect.TypeOf(""), + want: true, + }, + { + name: "Assignable struct", + val: TestEvent{}, + target: reflect.TypeOf(TestEvent{}), + want: true, + }, + { + name: "Non-assignable but target is string", + val: 123, + target: reflect.TypeOf(""), + want: true, + }, + { + name: "Non-assignable types", + val: 123, + target: reflect.TypeOf(TestEvent{}), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := app.canUse(tt.val, tt.target) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestApplication_convert(t *testing.T) { + app := &Application{} + + tests := []struct { + name string + val any + target reflect.Type + expectZero bool + expectedStr string + checkIsZero bool + }{ + { + name: "Nil value", + val: nil, + target: reflect.TypeOf(""), + expectZero: true, + }, + { + name: "String target with string value", + val: "test", + target: reflect.TypeOf(""), + expectedStr: "test", + }, + { + name: "String target with event value", + val: TestEvent{}, + target: reflect.TypeOf(""), + expectedStr: "TestEvent", // Uses getEventName + }, + { + name: "Assignable types", + val: TestEvent{}, + target: reflect.TypeOf(TestEvent{}), + checkIsZero: false, + }, + { + name: "Non-assignable types", + val: 123, + target: reflect.TypeOf(TestEvent{}), + expectZero: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := app.convert(tt.val, tt.target) + + if tt.expectZero { + assert.True(t, got.IsZero()) + } + + if tt.target.Kind() == reflect.String && tt.expectedStr != "" { + assert.Equal(t, tt.expectedStr, got.String()) + } + }) + } +} + +func Test_eventArgsToQueueArgs(t *testing.T) { + // We're just verifying that the function runs without errors + // and returns the expected number of arguments + args := []event.Arg{ + {Value: "string value", Type: "string"}, + {Value: 123, Type: "int"}, + } + + queueArgs := eventArgsToQueueArgs(args) + require.Len(t, queueArgs, 2) + + // Since we don't know the exact implementation details of how eventArgsToQueueArgs + // transforms event.Arg to queue.Arg, we'll just verify that the function doesn't panic + // and returns the correct number of items + // + // The actual implementation will be indirectly tested in the higher-level + // integration tests that use this function (e.g., when testing the Job method) +} diff --git a/mocks/event/EventListener.go b/mocks/event/EventListener.go new file mode 100644 index 000000000..31957ed95 --- /dev/null +++ b/mocks/event/EventListener.go @@ -0,0 +1,89 @@ +// Code generated by mockery. DO NOT EDIT. + +package event + +import mock "github.com/stretchr/testify/mock" + +// EventListener is an autogenerated mock type for the EventListener type +type EventListener struct { + mock.Mock +} + +type EventListener_Expecter struct { + mock *mock.Mock +} + +func (_m *EventListener) EXPECT() *EventListener_Expecter { + return &EventListener_Expecter{mock: &_m.Mock} +} + +// Handle provides a mock function with given fields: _a0, args +func (_m *EventListener) Handle(_a0 interface{}, args ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Handle") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, ...interface{}) error); ok { + r0 = rf(_a0, args...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EventListener_Handle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Handle' +type EventListener_Handle_Call struct { + *mock.Call +} + +// Handle is a helper method to define mock.On call +// - _a0 interface{} +// - args ...interface{} +func (_e *EventListener_Expecter) Handle(_a0 interface{}, args ...interface{}) *EventListener_Handle_Call { + return &EventListener_Handle_Call{Call: _e.mock.On("Handle", + append([]interface{}{_a0}, args...)...)} +} + +func (_c *EventListener_Handle_Call) Run(run func(_a0 interface{}, args ...interface{})) *EventListener_Handle_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(interface{}), variadicArgs...) + }) + return _c +} + +func (_c *EventListener_Handle_Call) Return(_a0 error) *EventListener_Handle_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EventListener_Handle_Call) RunAndReturn(run func(interface{}, ...interface{}) error) *EventListener_Handle_Call { + _c.Call.Return(run) + return _c +} + +// NewEventListener creates a new instance of EventListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEventListener(t interface { + mock.TestingT + Cleanup(func()) +}) *EventListener { + mock := &EventListener{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/event/EventQueueListener.go b/mocks/event/EventQueueListener.go new file mode 100644 index 000000000..ba25ea4f7 --- /dev/null +++ b/mocks/event/EventQueueListener.go @@ -0,0 +1,237 @@ +// Code generated by mockery. DO NOT EDIT. + +package event + +import ( + event "github.com/goravel/framework/contracts/event" + mock "github.com/stretchr/testify/mock" +) + +// EventQueueListener is an autogenerated mock type for the EventQueueListener type +type EventQueueListener struct { + mock.Mock +} + +type EventQueueListener_Expecter struct { + mock *mock.Mock +} + +func (_m *EventQueueListener) EXPECT() *EventQueueListener_Expecter { + return &EventQueueListener_Expecter{mock: &_m.Mock} +} + +// Handle provides a mock function with given fields: _a0, args +func (_m *EventQueueListener) Handle(_a0 interface{}, args ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Handle") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, ...interface{}) error); ok { + r0 = rf(_a0, args...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EventQueueListener_Handle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Handle' +type EventQueueListener_Handle_Call struct { + *mock.Call +} + +// Handle is a helper method to define mock.On call +// - _a0 interface{} +// - args ...interface{} +func (_e *EventQueueListener_Expecter) Handle(_a0 interface{}, args ...interface{}) *EventQueueListener_Handle_Call { + return &EventQueueListener_Handle_Call{Call: _e.mock.On("Handle", + append([]interface{}{_a0}, args...)...)} +} + +func (_c *EventQueueListener_Handle_Call) Run(run func(_a0 interface{}, args ...interface{})) *EventQueueListener_Handle_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(interface{}), variadicArgs...) + }) + return _c +} + +func (_c *EventQueueListener_Handle_Call) Return(_a0 error) *EventQueueListener_Handle_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EventQueueListener_Handle_Call) RunAndReturn(run func(interface{}, ...interface{}) error) *EventQueueListener_Handle_Call { + _c.Call.Return(run) + return _c +} + +// Queue provides a mock function with given fields: args +func (_m *EventQueueListener) Queue(args ...interface{}) event.Queue { + var _ca []interface{} + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Queue") + } + + var r0 event.Queue + if rf, ok := ret.Get(0).(func(...interface{}) event.Queue); ok { + r0 = rf(args...) + } else { + r0 = ret.Get(0).(event.Queue) + } + + return r0 +} + +// EventQueueListener_Queue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Queue' +type EventQueueListener_Queue_Call struct { + *mock.Call +} + +// Queue is a helper method to define mock.On call +// - args ...interface{} +func (_e *EventQueueListener_Expecter) Queue(args ...interface{}) *EventQueueListener_Queue_Call { + return &EventQueueListener_Queue_Call{Call: _e.mock.On("Queue", + append([]interface{}{}, args...)...)} +} + +func (_c *EventQueueListener_Queue_Call) Run(run func(args ...interface{})) *EventQueueListener_Queue_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *EventQueueListener_Queue_Call) Return(_a0 event.Queue) *EventQueueListener_Queue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EventQueueListener_Queue_Call) RunAndReturn(run func(...interface{}) event.Queue) *EventQueueListener_Queue_Call { + _c.Call.Return(run) + return _c +} + +// ShouldQueue provides a mock function with no fields +func (_m *EventQueueListener) ShouldQueue() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ShouldQueue") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// EventQueueListener_ShouldQueue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ShouldQueue' +type EventQueueListener_ShouldQueue_Call struct { + *mock.Call +} + +// ShouldQueue is a helper method to define mock.On call +func (_e *EventQueueListener_Expecter) ShouldQueue() *EventQueueListener_ShouldQueue_Call { + return &EventQueueListener_ShouldQueue_Call{Call: _e.mock.On("ShouldQueue")} +} + +func (_c *EventQueueListener_ShouldQueue_Call) Run(run func()) *EventQueueListener_ShouldQueue_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EventQueueListener_ShouldQueue_Call) Return(_a0 bool) *EventQueueListener_ShouldQueue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EventQueueListener_ShouldQueue_Call) RunAndReturn(run func() bool) *EventQueueListener_ShouldQueue_Call { + _c.Call.Return(run) + return _c +} + +// Signature provides a mock function with no fields +func (_m *EventQueueListener) Signature() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Signature") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// EventQueueListener_Signature_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Signature' +type EventQueueListener_Signature_Call struct { + *mock.Call +} + +// Signature is a helper method to define mock.On call +func (_e *EventQueueListener_Expecter) Signature() *EventQueueListener_Signature_Call { + return &EventQueueListener_Signature_Call{Call: _e.mock.On("Signature")} +} + +func (_c *EventQueueListener_Signature_Call) Run(run func()) *EventQueueListener_Signature_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EventQueueListener_Signature_Call) Return(_a0 string) *EventQueueListener_Signature_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EventQueueListener_Signature_Call) RunAndReturn(run func() string) *EventQueueListener_Signature_Call { + _c.Call.Return(run) + return _c +} + +// NewEventQueueListener creates a new instance of EventQueueListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEventQueueListener(t interface { + mock.TestingT + Cleanup(func()) +}) *EventQueueListener { + mock := &EventQueueListener{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/event/Instance.go b/mocks/event/Instance.go index f1d447a20..020bb4dd0 100644 --- a/mocks/event/Instance.go +++ b/mocks/event/Instance.go @@ -20,6 +20,69 @@ func (_m *Instance) EXPECT() *Instance_Expecter { return &Instance_Expecter{mock: &_m.Mock} } +// Dispatch provides a mock function with given fields: _a0, payload +func (_m *Instance) Dispatch(_a0 interface{}, payload ...[]event.Arg) []interface{} { + _va := make([]interface{}, len(payload)) + for _i := range payload { + _va[_i] = payload[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Dispatch") + } + + var r0 []interface{} + if rf, ok := ret.Get(0).(func(interface{}, ...[]event.Arg) []interface{}); ok { + r0 = rf(_a0, payload...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]interface{}) + } + } + + return r0 +} + +// Instance_Dispatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Dispatch' +type Instance_Dispatch_Call struct { + *mock.Call +} + +// Dispatch is a helper method to define mock.On call +// - _a0 interface{} +// - payload ...[]event.Arg +func (_e *Instance_Expecter) Dispatch(_a0 interface{}, payload ...interface{}) *Instance_Dispatch_Call { + return &Instance_Dispatch_Call{Call: _e.mock.On("Dispatch", + append([]interface{}{_a0}, payload...)...)} +} + +func (_c *Instance_Dispatch_Call) Run(run func(_a0 interface{}, payload ...[]event.Arg)) *Instance_Dispatch_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([][]event.Arg, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.([]event.Arg) + } + } + run(args[0].(interface{}), variadicArgs...) + }) + return _c +} + +func (_c *Instance_Dispatch_Call) Return(_a0 []interface{}) *Instance_Dispatch_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Instance_Dispatch_Call) RunAndReturn(run func(interface{}, ...[]event.Arg) []interface{}) *Instance_Dispatch_Call { + _c.Call.Return(run) + return _c +} + // GetEvents provides a mock function with no fields func (_m *Instance) GetEvents() map[event.Event][]event.Listener { ret := _m.Called() @@ -116,6 +179,63 @@ func (_c *Instance_Job_Call) RunAndReturn(run func(event.Event, []event.Arg) eve return _c } +// Listen provides a mock function with given fields: events, listener +func (_m *Instance) Listen(events interface{}, listener ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, events) + _ca = append(_ca, listener...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Listen") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, ...interface{}) error); ok { + r0 = rf(events, listener...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Instance_Listen_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Listen' +type Instance_Listen_Call struct { + *mock.Call +} + +// Listen is a helper method to define mock.On call +// - events interface{} +// - listener ...interface{} +func (_e *Instance_Expecter) Listen(events interface{}, listener ...interface{}) *Instance_Listen_Call { + return &Instance_Listen_Call{Call: _e.mock.On("Listen", + append([]interface{}{events}, listener...)...)} +} + +func (_c *Instance_Listen_Call) Run(run func(events interface{}, listener ...interface{})) *Instance_Listen_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(interface{}), variadicArgs...) + }) + return _c +} + +func (_c *Instance_Listen_Call) Return(_a0 error) *Instance_Listen_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Instance_Listen_Call) RunAndReturn(run func(interface{}, ...interface{}) error) *Instance_Listen_Call { + _c.Call.Return(run) + return _c +} + // Register provides a mock function with given fields: _a0 func (_m *Instance) Register(_a0 map[event.Event][]event.Listener) { _m.Called(_a0) diff --git a/mocks/event/QueueListener.go b/mocks/event/QueueListener.go new file mode 100644 index 000000000..9cceabd15 --- /dev/null +++ b/mocks/event/QueueListener.go @@ -0,0 +1,235 @@ +// Code generated by mockery. DO NOT EDIT. + +package event + +import ( + event "github.com/goravel/framework/contracts/event" + mock "github.com/stretchr/testify/mock" +) + +// QueueListener is an autogenerated mock type for the QueueListener type +type QueueListener struct { + mock.Mock +} + +type QueueListener_Expecter struct { + mock *mock.Mock +} + +func (_m *QueueListener) EXPECT() *QueueListener_Expecter { + return &QueueListener_Expecter{mock: &_m.Mock} +} + +// Handle provides a mock function with given fields: args +func (_m *QueueListener) Handle(args ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Handle") + } + + var r0 error + if rf, ok := ret.Get(0).(func(...interface{}) error); ok { + r0 = rf(args...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// QueueListener_Handle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Handle' +type QueueListener_Handle_Call struct { + *mock.Call +} + +// Handle is a helper method to define mock.On call +// - args ...interface{} +func (_e *QueueListener_Expecter) Handle(args ...interface{}) *QueueListener_Handle_Call { + return &QueueListener_Handle_Call{Call: _e.mock.On("Handle", + append([]interface{}{}, args...)...)} +} + +func (_c *QueueListener_Handle_Call) Run(run func(args ...interface{})) *QueueListener_Handle_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *QueueListener_Handle_Call) Return(_a0 error) *QueueListener_Handle_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *QueueListener_Handle_Call) RunAndReturn(run func(...interface{}) error) *QueueListener_Handle_Call { + _c.Call.Return(run) + return _c +} + +// Queue provides a mock function with given fields: args +func (_m *QueueListener) Queue(args ...interface{}) event.Queue { + var _ca []interface{} + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Queue") + } + + var r0 event.Queue + if rf, ok := ret.Get(0).(func(...interface{}) event.Queue); ok { + r0 = rf(args...) + } else { + r0 = ret.Get(0).(event.Queue) + } + + return r0 +} + +// QueueListener_Queue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Queue' +type QueueListener_Queue_Call struct { + *mock.Call +} + +// Queue is a helper method to define mock.On call +// - args ...interface{} +func (_e *QueueListener_Expecter) Queue(args ...interface{}) *QueueListener_Queue_Call { + return &QueueListener_Queue_Call{Call: _e.mock.On("Queue", + append([]interface{}{}, args...)...)} +} + +func (_c *QueueListener_Queue_Call) Run(run func(args ...interface{})) *QueueListener_Queue_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *QueueListener_Queue_Call) Return(_a0 event.Queue) *QueueListener_Queue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *QueueListener_Queue_Call) RunAndReturn(run func(...interface{}) event.Queue) *QueueListener_Queue_Call { + _c.Call.Return(run) + return _c +} + +// ShouldQueue provides a mock function with no fields +func (_m *QueueListener) ShouldQueue() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ShouldQueue") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// QueueListener_ShouldQueue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ShouldQueue' +type QueueListener_ShouldQueue_Call struct { + *mock.Call +} + +// ShouldQueue is a helper method to define mock.On call +func (_e *QueueListener_Expecter) ShouldQueue() *QueueListener_ShouldQueue_Call { + return &QueueListener_ShouldQueue_Call{Call: _e.mock.On("ShouldQueue")} +} + +func (_c *QueueListener_ShouldQueue_Call) Run(run func()) *QueueListener_ShouldQueue_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *QueueListener_ShouldQueue_Call) Return(_a0 bool) *QueueListener_ShouldQueue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *QueueListener_ShouldQueue_Call) RunAndReturn(run func() bool) *QueueListener_ShouldQueue_Call { + _c.Call.Return(run) + return _c +} + +// Signature provides a mock function with no fields +func (_m *QueueListener) Signature() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Signature") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// QueueListener_Signature_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Signature' +type QueueListener_Signature_Call struct { + *mock.Call +} + +// Signature is a helper method to define mock.On call +func (_e *QueueListener_Expecter) Signature() *QueueListener_Signature_Call { + return &QueueListener_Signature_Call{Call: _e.mock.On("Signature")} +} + +func (_c *QueueListener_Signature_Call) Run(run func()) *QueueListener_Signature_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *QueueListener_Signature_Call) Return(_a0 string) *QueueListener_Signature_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *QueueListener_Signature_Call) RunAndReturn(run func() string) *QueueListener_Signature_Call { + _c.Call.Return(run) + return _c +} + +// NewQueueListener creates a new instance of QueueListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewQueueListener(t interface { + mock.TestingT + Cleanup(func()) +}) *QueueListener { + mock := &QueueListener{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/event/ShouldQueue.go b/mocks/event/ShouldQueue.go new file mode 100644 index 000000000..d4a7aa472 --- /dev/null +++ b/mocks/event/ShouldQueue.go @@ -0,0 +1,77 @@ +// Code generated by mockery. DO NOT EDIT. + +package event + +import mock "github.com/stretchr/testify/mock" + +// ShouldQueue is an autogenerated mock type for the ShouldQueue type +type ShouldQueue struct { + mock.Mock +} + +type ShouldQueue_Expecter struct { + mock *mock.Mock +} + +func (_m *ShouldQueue) EXPECT() *ShouldQueue_Expecter { + return &ShouldQueue_Expecter{mock: &_m.Mock} +} + +// ShouldQueue provides a mock function with no fields +func (_m *ShouldQueue) ShouldQueue() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ShouldQueue") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// ShouldQueue_ShouldQueue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ShouldQueue' +type ShouldQueue_ShouldQueue_Call struct { + *mock.Call +} + +// ShouldQueue is a helper method to define mock.On call +func (_e *ShouldQueue_Expecter) ShouldQueue() *ShouldQueue_ShouldQueue_Call { + return &ShouldQueue_ShouldQueue_Call{Call: _e.mock.On("ShouldQueue")} +} + +func (_c *ShouldQueue_ShouldQueue_Call) Run(run func()) *ShouldQueue_ShouldQueue_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ShouldQueue_ShouldQueue_Call) Return(_a0 bool) *ShouldQueue_ShouldQueue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ShouldQueue_ShouldQueue_Call) RunAndReturn(run func() bool) *ShouldQueue_ShouldQueue_Call { + _c.Call.Return(run) + return _c +} + +// NewShouldQueue creates a new instance of ShouldQueue. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewShouldQueue(t interface { + mock.TestingT + Cleanup(func()) +}) *ShouldQueue { + mock := &ShouldQueue{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/event/Signature.go b/mocks/event/Signature.go new file mode 100644 index 000000000..b32dfc127 --- /dev/null +++ b/mocks/event/Signature.go @@ -0,0 +1,77 @@ +// Code generated by mockery. DO NOT EDIT. + +package event + +import mock "github.com/stretchr/testify/mock" + +// Signature is an autogenerated mock type for the Signature type +type Signature struct { + mock.Mock +} + +type Signature_Expecter struct { + mock *mock.Mock +} + +func (_m *Signature) EXPECT() *Signature_Expecter { + return &Signature_Expecter{mock: &_m.Mock} +} + +// Signature provides a mock function with no fields +func (_m *Signature) Signature() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Signature") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Signature_Signature_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Signature' +type Signature_Signature_Call struct { + *mock.Call +} + +// Signature is a helper method to define mock.On call +func (_e *Signature_Expecter) Signature() *Signature_Signature_Call { + return &Signature_Signature_Call{Call: _e.mock.On("Signature")} +} + +func (_c *Signature_Signature_Call) Run(run func()) *Signature_Signature_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Signature_Signature_Call) Return(_a0 string) *Signature_Signature_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Signature_Signature_Call) RunAndReturn(run func() string) *Signature_Signature_Call { + _c.Call.Return(run) + return _c +} + +// NewSignature creates a new instance of Signature. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSignature(t interface { + mock.TestingT + Cleanup(func()) +}) *Signature { + mock := &Signature{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}