Skip to content

MX-241: FIX propagate tenant context to async notification threads#151

Merged
IOhacker merged 1 commit intoopenMF:developfrom
DeathGun44:MX-241-harden-notification-smtp-fallback
Apr 16, 2026
Merged

MX-241: FIX propagate tenant context to async notification threads#151
IOhacker merged 1 commit intoopenMF:developfrom
DeathGun44:MX-241-harden-notification-smtp-fallback

Conversation

@DeathGun44
Copy link
Copy Markdown
Contributor

@DeathGun44 DeathGun44 commented Apr 16, 2026

Embed FineractPlatformTenant and business dates into SelfServiceNotificationEvent so async worker threads can restore the correct tenant context independently of the HTTP request thread lifecycle.

Root cause: afterCommit() callbacks fire after Fineract's auth filter clears ThreadLocalContextUtil, leaving the TaskDecorator with null tenant. Async notification threads therefore ran under [no-tenant] and failed on any DB lookup

Summary by CodeRabbit

  • New Features

    • Notifications now carry tenant and business-date context so async delivery preserves the originating tenant and business date.
  • Bug Fixes

    • Restored tenant/business-date context during notification processing to prevent context loss across async boundaries.
  • Tests

    • Added integration and unit tests validating tenant and business-date propagation and precedence.
    • Improved test isolation for SMTP fallback.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

Warning

Rate limit exceeded

@DeathGun44 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 38 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 43 minutes and 38 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86560be9-f497-478c-8b25-a39e964ec0de

📥 Commits

Reviewing files that changed from the base of the PR and between 23c1fac and 5eb40b6.

📒 Files selected for processing (9)
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationTenantPropagationIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java
📝 Walkthrough

Walkthrough

Pull request adds tenant and business-date context propagation to the self-service notification flow, extending events with tenant/business-date fields, capturing/restoring them across async boundaries, updating publishers and executor decoration, and adding tests for propagation behavior.

Changes

Cohort / File(s) Summary
Notification Event Enhancement
src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
Added immutable tenant and businessDates fields with getters, an expanded constructor that accepts FineractPlatformTenant and HashMap<BusinessDateType, LocalDate>, an updated delegating original constructor, and a withTenantContext(...) factory that captures thread-local tenant/business dates. toString() updated to include tenant.
Notification Service Context Restoration
src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
Added restoreTenantContext(event) invoked early in handleNotification(...) to set thread-local tenant and business dates from the event payload or log when none available. New imports for tenant/context utilities added.
Executor Task Decoration
src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
TaskDecorator now defensively captures tenant (swallowing IllegalStateException) and calls ThreadLocalContextUtil.setTenant(tenant) only when a non-null tenant was captured; business-date copying and reset remain.
Registration Notification Publishing
src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
confirmEnrollment() captures tenant and business-date snapshots (safely swallowing tenant/business-date read exceptions) after persisting the user and passes them into the SelfServiceNotificationEvent payload.
Authentication API Event Publishing
src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
Replaced direct new SelfServiceNotificationEvent(...) calls with SelfServiceNotificationEvent.withTenantContext(...) across login success/failure publication paths so factory captures thread-local context at publish time.
Tenant Propagation Integration Tests
src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationTenantPropagationIntegrationTest.java
Added integration tests validating tenant and business-date context propagation/restoration across async boundaries and precedence between event-carried tenant and executor-captured tenant; includes teardown to reset thread-local context.
Notification Service Unit Tests
src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
Added unit tests exercising restoration of tenant/business-dates from events, warn logging when none available, and withTenantContext(...) behavior with/without thread-local context.
Test Context Isolation
src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
Added @DirtiesContext to ensure test ApplicationContext is rebuilt for this class.
Token Format Test Correction
src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java
Updated assertions to use AssertJ and adjusted numeric-token length expectation to 6 digits (\d{6}) for clamped-minimum behavior.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AuthAPI as SelfAuthenticationApiResource
    participant ThreadLocal as ThreadLocalContextUtil
    participant Event as SelfServiceNotificationEvent
    participant Executor as ThreadPoolTaskExecutor
    participant NotifService as SelfServiceNotificationService
    participant Listener as NotificationListener

    Client->>AuthAPI: login request
    AuthAPI->>ThreadLocal: getTenant()/getBusinessDates()
    ThreadLocal-->>AuthAPI: tenant + dates (or error)
    AuthAPI->>Event: withTenantContext(...) factory (captures thread-local)
    Event-->>AuthAPI: event with tenant/dates
    AuthAPI->>Executor: publish event (async)
    Executor->>Executor: TaskDecorator may capture submitting-thread tenant
    Executor->>ThreadLocal: setTenant(captured) [if any]
    Executor->>Listener: execute listener -> NotifService.handleNotification(event)
    NotifService->>ThreadLocal: restoreTenantContext(event) -> setTenant(event.tenant), setBusinessDates(event.businessDates)
    NotifService->>NotifService: process notification under restored context
    NotifService->>ThreadLocal: reset() (cleanup)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

⏱️ 30-60 Min Review

Suggested reviewers

  • IOhacker
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.17% 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 pull request title directly and clearly describes the primary change: propagating tenant context to async notification threads, which is the main objective of the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java (1)

75-82: ⚠️ Potential issue | 🟠 Major

Guard the capture-side getTenant() call with try-catch.

The call to ThreadLocalContextUtil.getTenant() at line 76 does not guard against IllegalStateException, which is thrown when no tenant is bound. The TaskDecorator executes asynchronously and may run in a context without an active tenant binding. Other parts of the codebase (SelfServiceNotificationEvent.java lines 146–150 and SelfServiceNotificationService.java lines 272–276) already establish the pattern of wrapping this call in try-catch for IllegalStateException. Apply the same pattern here.

Proposed change
-            FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
+            FineractPlatformTenant tenant = null;
+            try {
+                tenant = ThreadLocalContextUtil.getTenant();
+            } catch (IllegalStateException ignored) {
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java`
around lines 75 - 82, Wrap the call to ThreadLocalContextUtil.getTenant() inside
a try-catch that catches IllegalStateException (as done elsewhere in
SelfServiceNotificationEvent and SelfServiceNotificationService) before
capturing tenant for the executor.setTaskDecorator lambda; if getTenant()
throws, set the captured tenant to null (or leave unset) so the returned
Runnable still checks tenant != null before calling
ThreadLocalContextUtil.setTenant(tenant). This ensures executor.setTaskDecorator
and the captured businessDates from currentBusinessDates() remain unchanged
while preventing uncaught IllegalStateException when no tenant is bound.
🧹 Nitpick comments (2)
src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java (1)

47-48: Use AssertJ assertion style for this test assertion.

Line 48 should use AssertJ instead of assertTrue(...) to match test standards.

Proposed change
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
...
-        assertTrue(token.matches("\\d{6}"));
+        assertThat(token).matches("\\d{6}");

As per coding guidelines, src/test/java/**: "Use JUnit 5, Mockito, and AssertJ."

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

In
`@src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java`
around lines 47 - 48, In SelfServiceAuthorizationTokenServiceTest replace the
JUnit assertTrue usage with an AssertJ assertion: locate the test where the
token is generated (in class SelfServiceAuthorizationTokenServiceTest) and
change assertTrue(token.matches("\\d{6}")) to an AssertJ form (e.g.
assertThat(token).matches("\\d{6}")), adding the static import from
org.assertj.core.api.Assertions if missing.
src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java (1)

61-73: Make businessDates unmodifiable before publishing the event.

The class is documented as immutable, but Lombok currently exposes the internal HashMap directly. That lets callers mutate the snapshot after publication, and the async listener will then consume the mutated state. Store this as a Map and wrap the defensive copy in an unmodifiable view.

Proposed change
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
@@
-    private final HashMap<BusinessDateType, LocalDate> businessDates;
+    private final Map<BusinessDateType, LocalDate> businessDates;
@@
-            FineractPlatformTenant tenant, HashMap<BusinessDateType, LocalDate> businessDates) {
+            FineractPlatformTenant tenant, HashMap<BusinessDateType, LocalDate> businessDates) {
@@
-        this.businessDates = businessDates != null ? new HashMap<>(businessDates) : null;
+        this.businessDates = businessDates != null ? Collections.unmodifiableMap(new HashMap<>(businessDates)) : null;

Also applies to: 112-127

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java`:
- Around line 534-545: The tenant snapshotting call in
SelfServiceRegistrationWritePlatformServiceImpl.confirmEnrollment uses
ThreadLocalContextUtil.getTenant() without handling IllegalStateException, which
can cause the transaction to rollback; update the code to mirror the defensive
pattern used for businessDatesSnapshot and
SelfServiceNotificationEvent.withTenantContext: wrap the getTenant() call in a
try-catch that catches IllegalStateException (or RuntimeException) and sets
tenantSnapshot to null on failure, leaving businessDatesSnapshot logic unchanged
so the notification remains best-effort and the enrollment transaction is not
rolled back.

In
`@src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationTenantPropagationIntegrationTest.java`:
- Around line 104-125: Test is currently duplicating production context
restoration by calling ThreadLocalContextUtil.setTenant(...) and
setBusinessDates(...) inside executor.execute, so replace that manual
restoration with invoking the real listener/service to exercise actual logic:
submit SelfServiceNotificationService.handleNotification(event) (or publish the
event via Spring application context) on the executor instead of calling
ThreadLocalContextUtil.setTenant/setBusinessDates, then keep the same
workerTenantId/workerBusinessDate reads and latch.countDown() in the finally to
assert observed values; also update the other identical test block that uses the
same manual restoration pattern to do the same replacement.

---

Outside diff comments:
In
`@src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java`:
- Around line 75-82: Wrap the call to ThreadLocalContextUtil.getTenant() inside
a try-catch that catches IllegalStateException (as done elsewhere in
SelfServiceNotificationEvent and SelfServiceNotificationService) before
capturing tenant for the executor.setTaskDecorator lambda; if getTenant()
throws, set the captured tenant to null (or leave unset) so the returned
Runnable still checks tenant != null before calling
ThreadLocalContextUtil.setTenant(tenant). This ensures executor.setTaskDecorator
and the captured businessDates from currentBusinessDates() remain unchanged
while preventing uncaught IllegalStateException when no tenant is bound.

---

Nitpick comments:
In
`@src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java`:
- Around line 47-48: In SelfServiceAuthorizationTokenServiceTest replace the
JUnit assertTrue usage with an AssertJ assertion: locate the test where the
token is generated (in class SelfServiceAuthorizationTokenServiceTest) and
change assertTrue(token.matches("\\d{6}")) to an AssertJ form (e.g.
assertThat(token).matches("\\d{6}")), adding the static import from
org.assertj.core.api.Assertions if missing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a3a37f41-6c7c-45f6-8072-dec689f96172

📥 Commits

Reviewing files that changed from the base of the PR and between 14ce2ca and 6b635a4.

📒 Files selected for processing (9)
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationTenantPropagationIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java

Comment on lines +104 to +125
executor.execute(() -> {
try {
if (event.getTenant() != null) {
ThreadLocalContextUtil.setTenant(event.getTenant());
}
if (event.getBusinessDates() != null) {
ThreadLocalContextUtil.setBusinessDates(event.getBusinessDates());
}
try {
workerTenantId.set(ThreadLocalContextUtil.getTenant().getTenantIdentifier());
} catch (IllegalStateException e) {
workerTenantId.set("NO_TENANT");
}
try {
workerBusinessDate.set(
ThreadLocalContextUtil.getBusinessDates().get(BusinessDateType.BUSINESS_DATE));
} catch (Exception ignored) {
}
} finally {
latch.countDown();
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Exercise the real listener instead of replaying the fix inside the test.

These assertions manually call ThreadLocalContextUtil.setTenant(event.getTenant()) and setBusinessDates(...) on the worker thread. That duplicates the production restoration logic, so the test still passes even if SelfServiceNotificationService.handleNotification() stops restoring context. Publish through Spring or run the real service on the executor so this actually guards the regression.

Also applies to: 179-188

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

In
`@src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationTenantPropagationIntegrationTest.java`
around lines 104 - 125, Test is currently duplicating production context
restoration by calling ThreadLocalContextUtil.setTenant(...) and
setBusinessDates(...) inside executor.execute, so replace that manual
restoration with invoking the real listener/service to exercise actual logic:
submit SelfServiceNotificationService.handleNotification(event) (or publish the
event via Spring application context) on the executor instead of calling
ThreadLocalContextUtil.setTenant/setBusinessDates, then keep the same
workerTenantId/workerBusinessDate reads and latch.countDown() in the finally to
assert observed values; also update the other identical test block that uses the
same manual restoration pattern to do the same replacement.

@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch from 6b635a4 to 23c1fac Compare April 16, 2026 04:03
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java (1)

534-548: Consider using imports instead of fully qualified class names.

The inline fully qualified class names reduce readability. Adding imports would be cleaner.

Suggested refactor

Add imports at the top of the file:

import java.time.LocalDate;
import java.util.HashMap;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;

Then simplify the snapshotting code:

-        org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant capturedTenant = null;
+        FineractPlatformTenant capturedTenant = null;
         try {
-            capturedTenant = org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil.getTenant();
+            capturedTenant = ThreadLocalContextUtil.getTenant();
         } catch (IllegalStateException ignored) {
         }
-        final org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant tenantSnapshot = capturedTenant;
-        final java.util.HashMap<org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType, java.time.LocalDate> businessDatesSnapshot;
-        java.util.HashMap<org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType, java.time.LocalDate> tempDates = null;
+        final FineractPlatformTenant tenantSnapshot = capturedTenant;
+        final HashMap<BusinessDateType, LocalDate> businessDatesSnapshot;
+        HashMap<BusinessDateType, LocalDate> tempDates = null;
         try {
-            java.util.HashMap<org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType, java.time.LocalDate> dates =
-                    org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil.getBusinessDates();
+            HashMap<BusinessDateType, LocalDate> dates = ThreadLocalContextUtil.getBusinessDates();
             tempDates = dates != null ? new java.util.HashMap<>(dates) : null;
         } catch (IllegalArgumentException e) {
         }
         businessDatesSnapshot = tempDates;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java`
around lines 534 - 548, Replace the repeated fully-qualified type names in
SelfServiceRegistrationWritePlatformServiceImpl with imports to improve
readability: add imports for FineractPlatformTenant, ThreadLocalContextUtil,
BusinessDateType, HashMap and LocalDate at the top of the file, then update the
snapshot block (where capturedTenant is assigned via
ThreadLocalContextUtil.getTenant() and businessDatesSnapshot is built via
ThreadLocalContextUtil.getBusinessDates()) to use the imported simple class
names (FineractPlatformTenant, ThreadLocalContextUtil, HashMap<BusinessDateType,
LocalDate>, LocalDate) instead of the long package-qualified names while keeping
the existing try/catch and assignment logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java`:
- Around line 534-548: Replace the repeated fully-qualified type names in
SelfServiceRegistrationWritePlatformServiceImpl with imports to improve
readability: add imports for FineractPlatformTenant, ThreadLocalContextUtil,
BusinessDateType, HashMap and LocalDate at the top of the file, then update the
snapshot block (where capturedTenant is assigned via
ThreadLocalContextUtil.getTenant() and businessDatesSnapshot is built via
ThreadLocalContextUtil.getBusinessDates()) to use the imported simple class
names (FineractPlatformTenant, ThreadLocalContextUtil, HashMap<BusinessDateType,
LocalDate>, LocalDate) instead of the long package-qualified names while keeping
the existing try/catch and assignment logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ec20a3f9-376c-4abb-b22b-28c6f63b9383

📥 Commits

Reviewing files that changed from the base of the PR and between 6b635a4 and 23c1fac.

📒 Files selected for processing (9)
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationTenantPropagationIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/registration/service/SelfServiceAuthorizationTokenServiceTest.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationTenantPropagationIntegrationTest.java

@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch 2 times, most recently from b43a178 to 23c1fac Compare April 16, 2026 04:14
…eads

Embed FineractPlatformTenant and business dates into SelfServiceNotificationEvent
so async worker threads can restore the correct tenant context independently of
the HTTP request thread lifecycle.

Root cause: afterCommit() callbacks fire after Fineract's auth filter clears
ThreadLocalContextUtil, leaving the TaskDecorator with null tenant. Async
notification threads therefore ran under [no-tenant] and failed on any
DB lookup.

Changes:
- SelfServiceNotificationEvent: add tenant/businessDates fields + withTenantContext() factory
- SelfServiceNotificationService: restore tenant from event at top of handleNotification()
- SelfServiceRegistrationWritePlatformServiceImpl: capture tenant snapshot before afterCommit()
- SelfAuthenticationApiResource: use withTenantContext() factory for all event publications
- SelfServiceNotificationConfig: guard TaskDecorator with null-check (belt-and-suspenders)
- SelfServicePluginEmailService: wrap SmtpConfigurationUnavailableException in PlatformEmailSendException;
  enable mail.smtp.auth only when username is present

Tests:
- SelfServiceNotificationServiceTest: 4 new unit tests for tenant restoration
- SelfServiceNotificationTenantPropagationIntegrationTest: 3 new integration tests
  simulating full afterCommit → @async → listener lifecycle
- SelfServiceSmtpFallbackIntegrationTest: add @DirtiesContext to prevent state leakage
- SelfServiceAuthorizationTokenServiceTest: align with upstream MIN_NUMERIC_LENGTH=6
@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch from 23c1fac to 5eb40b6 Compare April 16, 2026 04:20
@IOhacker IOhacker merged commit f9d7248 into openMF:develop Apr 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants