Skip to content

disable more event bus subscribers in test#5009

Merged
IvanIvanoff merged 6 commits intomasterfrom
disable-more-event-bus-subscribers-in-test
Feb 19, 2026
Merged

disable more event bus subscribers in test#5009
IvanIvanoff merged 6 commits intomasterfrom
disable-more-event-bus-subscribers-in-test

Conversation

@IvanIvanoff
Copy link
Member

@IvanIvanoff IvanIvanoff commented Feb 18, 2026

  • Refactor how EventBus subscribers are disabeld in test
  • Enable KafkaExporterSubscriber only where needed in test
  • Disable MetricRegistrySubscriber in test and enable it where needed

Changes

Ticket

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have tried to find clearer solution before commenting hard-to-understand parts of code
  • I have added tests that prove my fix is effective or that my feature works

Summary by CodeRabbit

  • New Features

    • Runtime management for event subscribers (subscribe/unsubscribe at runtime).
  • Refactor

    • Event subscriber config switched from per-flag booleans to include/omit list semantics (extra/disabled lists).
    • Consolidated subscriber setup/teardown across tests for consistent lifecycle management.
    • Added a no-op test subscriber to prevent missing-subscriber warnings during tests.

Add a NoopSubscriber that subscribers to all topics as otherwise
when KafkaExporterSubscriber is disabled, a warning that events without
subscribers is written for many tests.
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Replaces boolean subscriber flags with list-based extra_subscribers and disabled_subscribers; adds runtime APIs subscribe_subscriber/1 and unsubscribe_subscriber/1; introduces Sanbase.EventBus.NoopSubscriber; updates tests to use new APIs and adds global test setup/teardown.

Changes

Cohort / File(s) Summary
Config & Core EventBus
config/test.exs, lib/sanbase/event_bus/event_bus.ex, lib/sanbase/event_bus/noop_subscriber.ex
Swapped per-subscriber booleans for extra_subscribers and disabled_subscribers; build subscriber lists from config; register topics and subscribe allowed subscribers at init; added subscribe_subscriber/1 and unsubscribe_subscriber/1; added NoopSubscriber that consumes all topics and marks events completed.
Tests — subscriber API usage & global setup
test/sanbase/app_notifications/app_notifications_test.exs, test/sanbase/event_bus/kafka_exporter_subscriber_test.exs, test/sanbase/metric_registry/metric_registry_test.exs, test/sanbase/telegram/telegram_test.exs, test/sanbase_web/channels/notifications_channel_test.exs, test/sanbase_web/controllers/mailjet_controller_test.exs, test/sanbase_web/graphql/app_notifications/app_notification_api_test.exs, test/sanbase_web/graphql/user/user_settings_test.exs, test/sanbase/email/emails_test.exs, test/sanbase_web/api_call_limit/api_call_limit_api_test.exs, test/sanbase_web/graphql/billing/subscribe_api_test.exs, test/sanbase_web/stripe_webhook_test.exs
Replaced direct EventBus.subscribe({mod, mod.topics()}) / EventBus.unsubscribe(mod) calls with Sanbase.EventBus.subscribe_subscriber/1 and unsubscribe_subscriber/1; added several setup_all blocks subscribing subscribers once and registering teardown to drain topics and unsubscribe.

Sequence Diagram(s)

sequenceDiagram
    participant Config
    participant EventBus
    participant Subscriber
    participant TestRunner

    Config->>EventBus: load `extra_subscribers` and `disabled_subscribers`
    EventBus->>EventBus: build subscriber list (base ++ extra) and compute startup subscribers
    EventBus->>EventBus: register topics for all subscribers
    EventBus->>Subscriber: subscribe at startup (for allowed subscribers)
    Note right of EventBus: runtime subscribe_subscriber/ unsubscribe_subscriber helpers
    TestRunner->>EventBus: subscribe_subscriber(Subscriber)
    EventBus->>Subscriber: EventBus.subscribe({Subscriber, Subscriber.topics()})
    TestRunner->>EventBus: unsubscribe_subscriber(Subscriber)
    EventBus->>Subscriber: EventBus.unsubscribe(Subscriber)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Suggested reviewers

  • tspenov

Poem

🐰 I nibbled through booleans, neat and spry,
Lists hopped in, subscribers multiply,
Noop ears listen, keeping tests serene,
Events marked done — quiet, clean, and keen,
Hooray — a rabbit-approved EventBus scene! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the pull request - disabling more EventBus subscribers in the test environment by refactoring the configuration approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch disable-more-event-bus-subscribers-in-test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
lib/sanbase/event_bus/noop_subscriber.ex (2)

1-7: Consider relocating to test/support/ given its test-only nature.

The @moduledoc explicitly states this module is only for tests, but placing it under lib/ means it gets compiled into the production application. Modules intended exclusively for tests typically belong in test/support/.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sanbase/event_bus/noop_subscriber.ex` around lines 1 - 7,
Sanbase.EventBus.NoopSubscriber is test-only but lives under lib/, so move its
source out of production code into the test tree (e.g.,
test/support/event_bus/noop_subscriber.ex) and remove it from lib/, then update
any tests/setup that reference it (or ensure test/support is compiled by ExUnit)
so the module remains available in tests but is not compiled into the production
release.

10-23: Add @doc and @spec to public functions.

topics/0, start_link/1, and process/1 are all public but lack documentation and typespecs.

📝 Proposed addition
+  `@doc` "Subscribes to all EventBus topics."
+  `@spec` topics() :: [String.t()]
   def topics(), do: [".*"]
 
+  `@doc` "Starts the NoopSubscriber GenServer."
+  `@spec` start_link(keyword()) :: GenServer.on_start()
   def start_link(opts \\ []) do
     GenServer.start_link(__MODULE__, opts, name: Keyword.get(opts, :name, __MODULE__))
   end
 
   def init(opts) do
     {:ok, opts}
   end
 
+  `@doc` "Marks the event as completed without any side effects."
+  `@spec` process({atom(), any()}) :: :ok
   def process({_topic, _id} = event_shadow) do
     spawn(fn -> EventBus.mark_as_completed({__MODULE__, event_shadow}) end)
     :ok
   end

As per coding guidelines: "Add typespecs (@spec) to all public functions" and "Add @doc documentation to all public functions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sanbase/event_bus/noop_subscriber.ex` around lines 10 - 23, Add `@doc` and
`@spec` annotations for the public functions topics/0, start_link/1, and
process/1: document that topics/0 returns a list of topic patterns (e.g.,
[String.t()]) and add `@spec` topics() :: [String.t()]; document start_link/1
(accepts opts :: keyword() with optional :name) and add `@spec`
start_link(keyword()) :: GenServer.on_start(); document process/1 (accepts an
event tuple like {topic :: String.t(), id :: any()}) and add `@spec`
process({String.t(), any()}) :: :ok, plus brief `@doc` text for each describing
purpose/usage and that process/1 spawns EventBus.mark_as_completed({__MODULE__,
event_shadow}).
lib/sanbase/event_bus/event_bus.ex (1)

79-85: Add @doc and @spec to new public functions.

Both subscribe_subscriber/1 and unsubscribe_subscriber/1 are public API but lack documentation and typespecs, which are required by the project guidelines.

📝 Proposed addition
+  `@doc` """
+  Subscribes a subscriber module to its declared topics.
+  The subscriber module must implement a `topics/0` function.
+  """
+  `@spec` subscribe_subscriber(module()) :: :ok
   def subscribe_subscriber(subscriber) do
     EventBus.subscribe({subscriber, subscriber.topics()})
   end
 
+  `@doc` """
+  Unsubscribes a subscriber module from all topics.
+  """
+  `@spec` unsubscribe_subscriber(module()) :: :ok
   def unsubscribe_subscriber(subscriber) do
     EventBus.unsubscribe(subscriber)
   end

As per coding guidelines: "Add typespecs (@spec) to all public functions" and "Add @doc documentation to all public functions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sanbase/event_bus/event_bus.ex` around lines 79 - 85, Add `@doc` and `@spec`
for the two new public functions: document that subscribe_subscriber/1 calls
EventBus.subscribe with the subscriber and its topics and that
unsubscribe_subscriber/1 calls EventBus.unsubscribe for the subscriber; add
`@specs` declaring the subscriber parameter as module() (or a more specific
behaviour type if available) and a return type (e.g., term() or :ok | {:error,
term()} depending on EventBus API) so both subscribe_subscriber/1 and
unsubscribe_subscriber/1 have proper docs and typespecs referencing
EventBus.subscribe/1 and EventBus.unsubscribe/1.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/sanbase/metric_registry/metric_registry_test.exs`:
- Around line 2-13: The test module uses setup_all which on exit calls
Sanbase.EventBus.drain_topics/2 (which must only be invoked from non-async
tests), so update the module declaration to run synchronously by adding async:
false to the test case invocation (i.e., change the use Sanbase.DataCase line to
include async: false); keep the existing setup_all and on_exit logic
(Sanbase.EventBus.subscribe_subscriber and Sanbase.EventBus.drain_topics/2)
intact.

---

Nitpick comments:
In `@lib/sanbase/event_bus/event_bus.ex`:
- Around line 79-85: Add `@doc` and `@spec` for the two new public functions:
document that subscribe_subscriber/1 calls EventBus.subscribe with the
subscriber and its topics and that unsubscribe_subscriber/1 calls
EventBus.unsubscribe for the subscriber; add `@specs` declaring the subscriber
parameter as module() (or a more specific behaviour type if available) and a
return type (e.g., term() or :ok | {:error, term()} depending on EventBus API)
so both subscribe_subscriber/1 and unsubscribe_subscriber/1 have proper docs and
typespecs referencing EventBus.subscribe/1 and EventBus.unsubscribe/1.

In `@lib/sanbase/event_bus/noop_subscriber.ex`:
- Around line 1-7: Sanbase.EventBus.NoopSubscriber is test-only but lives under
lib/, so move its source out of production code into the test tree (e.g.,
test/support/event_bus/noop_subscriber.ex) and remove it from lib/, then update
any tests/setup that reference it (or ensure test/support is compiled by ExUnit)
so the module remains available in tests but is not compiled into the production
release.
- Around line 10-23: Add `@doc` and `@spec` annotations for the public functions
topics/0, start_link/1, and process/1: document that topics/0 returns a list of
topic patterns (e.g., [String.t()]) and add `@spec` topics() :: [String.t()];
document start_link/1 (accepts opts :: keyword() with optional :name) and add
`@spec` start_link(keyword()) :: GenServer.on_start(); document process/1 (accepts
an event tuple like {topic :: String.t(), id :: any()}) and add `@spec`
process({String.t(), any()}) :: :ok, plus brief `@doc` text for each describing
purpose/usage and that process/1 spawns EventBus.mark_as_completed({__MODULE__,
event_shadow}).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
test/sanbase_web/graphql/billing/subscribe_api_test.exs (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Same async concern as emails_test.exs — add explicit async: false.

Line 2 uses use SanbaseWeb.ConnCase without async: false. The test body uses with_mock/with_mocks (global module patching) and the on_exit calls drain_topics, both of which require async: false. Add the annotation to make the guarantee explicit and consistent with the two other test files in this PR that already set it:

-  use SanbaseWeb.ConnCase
+  use SanbaseWeb.ConnCase, async: false

Also applies to: 19-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/sanbase_web/graphql/billing/subscribe_api_test.exs` at line 2, The test
module uses global mocks (with_mock/with_mocks) and on_exit cleanup
(drain_topics) so update the test declaration to disable async by passing async:
false to the ConnCase usage; specifically change the use SanbaseWeb.ConnCase
invocation to include async: false so the test module runs synchronously and
avoids race conditions with global module patching and on_exit cleanup.
test/sanbase/email/emails_test.exs (1)

2-2: ⚠️ Potential issue | 🟠 Major

Add async: false to ConnCase — required for drain_topics and setup_with_mocks

The test module calls Sanbase.EventBus.drain_topics/2 in its setup_all on_exit callback and uses setup_with_mocks for global mocking. Both require async: false to be safe:

  • drain_topics carries an inline comment: "Invoke this only from async: false tests!" and operates on shared ETS table state.
  • setup_with_mocks patches modules globally, which is incompatible with concurrent test execution.

Without async: false, concurrent test modules can race when cleaning up subscribers, causing one module's unsubscribe_subscriber to remove the subscriber while other modules' tests are still in flight.

Suggested fix:
-  use SanbaseWeb.ConnCase
+  use SanbaseWeb.ConnCase, async: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/sanbase/email/emails_test.exs` at line 2, The test module must run
synchronously to safely call Sanbase.EventBus.drain_topics/2 in its setup_all
on_exit and to use setup_with_mocks; update the module's ConnCase invocation
from use SanbaseWeb.ConnCase to use SanbaseWeb.ConnCase, async: false so the
tests are not run concurrently and shared ETS/global mocks are safe (this
affects the test file that calls drain_topics/2 and setup_with_mocks).
🧹 Nitpick comments (1)
test/sanbase_web/api_call_limit/api_call_limit_api_test.exs (1)

19-23: Optional: extract repeated setup_all boilerplate into a shared helper.

The same 8-line setup_all + on_exit block appears verbatim in all four test files changed by this PR (api_call_limit_api_test.exs, emails_test.exs, subscribe_api_test.exs, stripe_webhook_test.exs). Extracting it would make future changes (e.g., adjusting the drain timeout) a single-site update:

# In a shared test support module, e.g. test/support/event_bus_helpers.ex
+defmodule Sanbase.Test.EventBusHelpers do
+  def subscribe_billing_subscriber do
+    subscriber = Sanbase.EventBus.BillingEventSubscriber
+    Sanbase.EventBus.subscribe_subscriber(subscriber)
+
+    ExUnit.Callbacks.on_exit(fn ->
+      Sanbase.EventBus.drain_topics(subscriber.topics(), 10_000)
+      Sanbase.EventBus.unsubscribe_subscriber(subscriber)
+    end)
+  end
+end

Then each test module reduces to a single setup_all call:

-  setup_all do
-    subscriber = Sanbase.EventBus.BillingEventSubscriber
-    Sanbase.EventBus.subscribe_subscriber(subscriber)
-    on_exit(fn ->
-      Sanbase.EventBus.drain_topics(subscriber.topics(), 10_000)
-      Sanbase.EventBus.unsubscribe_subscriber(subscriber)
-    end)
-  end
+  setup_all do: Sanbase.Test.EventBusHelpers.subscribe_billing_subscriber()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/sanbase_web/api_call_limit/api_call_limit_api_test.exs` around lines 19
- 23, The repeated setup_all + on_exit block (which sets and deletes Application
env via Application.delete_env(SanbaseWeb.Graphql.AbsintheBeforeSend,
:api_call_exporting_enabled) and manages the Sanbase.EventBus subscriber with
Sanbase.EventBus.drain_topics/2 and Sanbase.EventBus.unsubscribe_subscriber/1)
should be extracted into a shared test helper function (e.g.,
TestHelpers.setup_eventbus_and_env or ApiTestSupport.setup_eventbus_and_env)
that returns the same setup_all callback; replace each module's inline setup_all
with a single call to setup_all(TestHelpers.setup_eventbus_and_env()) so future
changes (like timeout) are made in one place and tests use the helper instead of
duplicating the 8-line block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@test/sanbase_web/graphql/billing/subscribe_api_test.exs`:
- Line 2: The test module uses global mocks (with_mock/with_mocks) and on_exit
cleanup (drain_topics) so update the test declaration to disable async by
passing async: false to the ConnCase usage; specifically change the use
SanbaseWeb.ConnCase invocation to include async: false so the test module runs
synchronously and avoids race conditions with global module patching and on_exit
cleanup.

In `@test/sanbase/email/emails_test.exs`:
- Line 2: The test module must run synchronously to safely call
Sanbase.EventBus.drain_topics/2 in its setup_all on_exit and to use
setup_with_mocks; update the module's ConnCase invocation from use
SanbaseWeb.ConnCase to use SanbaseWeb.ConnCase, async: false so the tests are
not run concurrently and shared ETS/global mocks are safe (this affects the test
file that calls drain_topics/2 and setup_with_mocks).

---

Nitpick comments:
In `@test/sanbase_web/api_call_limit/api_call_limit_api_test.exs`:
- Around line 19-23: The repeated setup_all + on_exit block (which sets and
deletes Application env via
Application.delete_env(SanbaseWeb.Graphql.AbsintheBeforeSend,
:api_call_exporting_enabled) and manages the Sanbase.EventBus subscriber with
Sanbase.EventBus.drain_topics/2 and Sanbase.EventBus.unsubscribe_subscriber/1)
should be extracted into a shared test helper function (e.g.,
TestHelpers.setup_eventbus_and_env or ApiTestSupport.setup_eventbus_and_env)
that returns the same setup_all callback; replace each module's inline setup_all
with a single call to setup_all(TestHelpers.setup_eventbus_and_env()) so future
changes (like timeout) are made in one place and tests use the helper instead of
duplicating the 8-line block.

@IvanIvanoff IvanIvanoff force-pushed the disable-more-event-bus-subscribers-in-test branch from 9a9c4ab to a9043a9 Compare February 19, 2026 07:11
@IvanIvanoff IvanIvanoff merged commit 517abfd into master Feb 19, 2026
3 checks passed
@IvanIvanoff IvanIvanoff deleted the disable-more-event-bus-subscribers-in-test branch February 19, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments