chore(event-bus-*): Do not emit if no subscribers: (#14084)
* chore(event-vus-*): Do not emit if no subscribers: * Create curly-apricots-double.md * add tests * align tests
This commit is contained in:
@@ -3,13 +3,6 @@ import { Queue, Worker } from "bullmq"
|
||||
import { Redis } from "ioredis"
|
||||
import RedisEventBusService from "../event-bus-redis"
|
||||
|
||||
// const redisURL = "redis://localhost:6379"
|
||||
// const client = new Redis(6379, redisURL, {
|
||||
// // Lazy connect to properly handle connection errors
|
||||
// lazyConnect: true,
|
||||
// maxRetriesPerRequest: 0,
|
||||
// })
|
||||
|
||||
jest.mock("bullmq")
|
||||
jest.mock("ioredis")
|
||||
|
||||
@@ -96,6 +89,8 @@ describe("RedisEventBusService", () => {
|
||||
})
|
||||
|
||||
it("should add job to queue with default options", async () => {
|
||||
eventBus.subscribe("eventName", () => Promise.resolve())
|
||||
|
||||
await eventBus.emit([
|
||||
{
|
||||
name: "eventName",
|
||||
@@ -119,6 +114,8 @@ describe("RedisEventBusService", () => {
|
||||
})
|
||||
|
||||
it("should add job to queue with custom options passed directly upon emitting", async () => {
|
||||
eventBus.subscribe("eventName", () => Promise.resolve())
|
||||
|
||||
await eventBus.emit([{ name: "eventName", data: { hi: "1234" } }], {
|
||||
attempts: 3,
|
||||
backoff: 5000,
|
||||
@@ -158,6 +155,8 @@ describe("RedisEventBusService", () => {
|
||||
queue = (eventBus as any).queue_
|
||||
queue.addBulk = jest.fn()
|
||||
|
||||
eventBus.subscribe("eventName", () => Promise.resolve())
|
||||
|
||||
await eventBus.emit(
|
||||
[
|
||||
{
|
||||
@@ -202,6 +201,8 @@ describe("RedisEventBusService", () => {
|
||||
queue = (eventBus as any).queue_
|
||||
queue.addBulk = jest.fn()
|
||||
|
||||
eventBus.subscribe("eventName", () => Promise.resolve())
|
||||
|
||||
await eventBus.emit(
|
||||
{
|
||||
name: "eventName",
|
||||
@@ -268,12 +269,15 @@ describe("RedisEventBusService", () => {
|
||||
},
|
||||
]
|
||||
|
||||
eventBus.subscribe("ungrouped-event-2", () => Promise.resolve())
|
||||
eventBus.subscribe("grouped-event-1", () => Promise.resolve())
|
||||
eventBus.subscribe("grouped-event-2", () => Promise.resolve())
|
||||
eventBus.subscribe("grouped-event-3", () => Promise.resolve())
|
||||
|
||||
redis.unlink = jest.fn()
|
||||
|
||||
await eventBus.emit(events, options)
|
||||
|
||||
// Expect 1 event to have been send
|
||||
// Expect 2 pushes to redis as there are 2 groups of events to push
|
||||
expect(queue.addBulk).toHaveBeenCalledTimes(1)
|
||||
expect(redis.rpush).toHaveBeenCalledTimes(2)
|
||||
expect(redis.unlink).not.toHaveBeenCalled()
|
||||
@@ -331,6 +335,149 @@ describe("RedisEventBusService", () => {
|
||||
expect(redis.unlink).toHaveBeenCalledWith("staging:test-group-2")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Events without subscribers", () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
eventBus = new RedisEventBusService(moduleDeps, simpleModuleOptions, {
|
||||
scope: "internal",
|
||||
})
|
||||
|
||||
queue = (eventBus as any).queue_
|
||||
queue.addBulk = jest.fn()
|
||||
redis = (eventBus as any).eventBusRedisConnection_
|
||||
redis.rpush = jest.fn()
|
||||
})
|
||||
|
||||
it("should not add events to queue when there are no subscribers", async () => {
|
||||
await eventBus.emit([
|
||||
{
|
||||
name: "eventWithoutSubscribers",
|
||||
data: { test: "data" },
|
||||
},
|
||||
])
|
||||
|
||||
expect(queue.addBulk).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should still call interceptors even when there are no subscribers", async () => {
|
||||
const callInterceptorsSpy = jest.spyOn(
|
||||
eventBus as any,
|
||||
"callInterceptors"
|
||||
)
|
||||
|
||||
await eventBus.emit([
|
||||
{
|
||||
name: "eventWithoutSubscribers",
|
||||
data: { test: "data" },
|
||||
},
|
||||
])
|
||||
|
||||
expect(callInterceptorsSpy).toHaveBeenCalledTimes(1)
|
||||
expect(callInterceptorsSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: "eventWithoutSubscribers",
|
||||
data: { test: "data" },
|
||||
},
|
||||
{ isGrouped: false }
|
||||
)
|
||||
|
||||
expect(queue.addBulk).not.toHaveBeenCalled()
|
||||
|
||||
callInterceptorsSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should add events to queue only for events with subscribers using wildcard", async () => {
|
||||
eventBus.subscribe("*", () => Promise.resolve())
|
||||
|
||||
await eventBus.emit([
|
||||
{
|
||||
name: "anyEvent",
|
||||
data: { test: "data" },
|
||||
},
|
||||
])
|
||||
|
||||
expect(queue.addBulk).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should not add grouped events to queue when releasing if there are no subscribers", async () => {
|
||||
const options = { delay: 1000 }
|
||||
const event = {
|
||||
name: "grouped-event-no-sub",
|
||||
data: { hi: "1234" },
|
||||
metadata: { eventGroupId: "test-group-no-sub" },
|
||||
}
|
||||
|
||||
await eventBus.emit(event, options)
|
||||
|
||||
expect(redis.rpush).toHaveBeenCalledTimes(1)
|
||||
expect(queue.addBulk).not.toHaveBeenCalled()
|
||||
|
||||
const [builtEvent] = (eventBus as any).buildEvents([event], options)
|
||||
|
||||
redis.lrange = jest.fn((key) => {
|
||||
if (key === "staging:test-group-no-sub") {
|
||||
return Promise.resolve([JSON.stringify(builtEvent)])
|
||||
}
|
||||
return Promise.resolve([])
|
||||
})
|
||||
|
||||
redis.unlink = jest.fn()
|
||||
queue.addBulk = jest.fn()
|
||||
|
||||
await eventBus.releaseGroupedEvents("test-group-no-sub")
|
||||
|
||||
expect(queue.addBulk).not.toHaveBeenCalled()
|
||||
|
||||
expect(redis.unlink).toHaveBeenCalledWith("staging:test-group-no-sub")
|
||||
})
|
||||
|
||||
it("should still call interceptors for grouped events without subscribers", async () => {
|
||||
const options = { delay: 1000 }
|
||||
const event = {
|
||||
name: "grouped-event-no-sub-2",
|
||||
data: { hi: "1234" },
|
||||
metadata: { eventGroupId: "test-group-no-sub-2" },
|
||||
}
|
||||
|
||||
await eventBus.emit(event, options)
|
||||
|
||||
const [builtEvent] = (eventBus as any).buildEvents([event], options)
|
||||
|
||||
redis.lrange = jest.fn((key) => {
|
||||
if (key === "staging:test-group-no-sub-2") {
|
||||
return Promise.resolve([JSON.stringify(builtEvent)])
|
||||
}
|
||||
return Promise.resolve([])
|
||||
})
|
||||
|
||||
redis.unlink = jest.fn()
|
||||
queue.addBulk = jest.fn()
|
||||
|
||||
const callInterceptorsSpy = jest.spyOn(
|
||||
eventBus as any,
|
||||
"callInterceptors"
|
||||
)
|
||||
|
||||
await eventBus.releaseGroupedEvents("test-group-no-sub-2")
|
||||
|
||||
expect(callInterceptorsSpy).toHaveBeenCalledTimes(1)
|
||||
expect(callInterceptorsSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: "grouped-event-no-sub-2",
|
||||
}),
|
||||
{
|
||||
isGrouped: true,
|
||||
eventGroupId: "test-group-no-sub-2",
|
||||
}
|
||||
)
|
||||
|
||||
expect(queue.addBulk).not.toHaveBeenCalled()
|
||||
|
||||
callInterceptorsSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("worker_", () => {
|
||||
@@ -352,7 +499,6 @@ describe("RedisEventBusService", () => {
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
// TODO: The typing for this is all over the place
|
||||
await eventBus.worker_({
|
||||
name: "eventName",
|
||||
data: { data: { test: 1 } },
|
||||
|
||||
@@ -162,9 +162,17 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
||||
this.callInterceptors(eventData, { isGrouped: false })
|
||||
)
|
||||
|
||||
const emitData = this.buildEvents(eventsToEmit, options)
|
||||
const eventsWithSubscribers = eventsToEmit.filter((eventData) => {
|
||||
const eventSubscribers =
|
||||
this.eventToSubscribersMap.get(eventData.name) || []
|
||||
const wildcardSubscribers = this.eventToSubscribersMap.get("*") || []
|
||||
return eventSubscribers.length || wildcardSubscribers.length
|
||||
})
|
||||
|
||||
promises.push(this.queue_.addBulk(emitData))
|
||||
if (eventsWithSubscribers.length) {
|
||||
const emitData = this.buildEvents(eventsWithSubscribers, options)
|
||||
promises.push(this.queue_.addBulk(emitData))
|
||||
}
|
||||
}
|
||||
|
||||
for (const [groupId, events] of groupEventsMap.entries()) {
|
||||
@@ -220,7 +228,6 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
||||
// Call interceptors before emitting grouped events
|
||||
// Extract the original messages from the job data structure
|
||||
groupedEvents.map((jobData) => {
|
||||
// Reconstruct the message from the job data
|
||||
const message = {
|
||||
name: jobData.name,
|
||||
data: jobData.data,
|
||||
@@ -232,7 +239,16 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
||||
})
|
||||
})
|
||||
|
||||
await this.queue_.addBulk(groupedEvents)
|
||||
const eventsWithSubscribers = groupedEvents.filter((jobData) => {
|
||||
const eventSubscribers =
|
||||
this.eventToSubscribersMap.get(jobData.name) || []
|
||||
const wildcardSubscribers = this.eventToSubscribersMap.get("*") || []
|
||||
return eventSubscribers.length || wildcardSubscribers.length
|
||||
})
|
||||
|
||||
if (eventsWithSubscribers.length) {
|
||||
await this.queue_.addBulk(eventsWithSubscribers)
|
||||
}
|
||||
|
||||
await this.clearGroupedEvents(eventGroupId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user