feat: support smtp interception - #803
Conversation
…type.addRequest`
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
📝 WalkthroughWalkthroughChangesAdds a complete SMTP interceptor with typed sessions and events, SMTP command and reply parsing, authentication and message handling, dot-stuffing, upstream delivery replay, passthrough control, error handling, and extensive Nodemailer and Resend integration tests. SMTP protocol and session model
Interceptor and validation
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Nodemailer
participant SmtpInterceptor
participant SmtpClientConnection
participant SmtpServerConnection
participant RealSMTPServer
Nodemailer->>SmtpInterceptor: Connect to SMTP endpoint
SmtpInterceptor->>SmtpClientConnection: Create intercepted session
Nodemailer->>SmtpClientConnection: Send SMTP commands and message
SmtpClientConnection->>SmtpInterceptor: Emit session and command events
SmtpInterceptor->>SmtpServerConnection: Connect or enable passthrough
SmtpServerConnection->>RealSMTPServer: Replay SMTP transaction
RealSMTPServer-->>SmtpServerConnection: Return phase replies and queue ID
SmtpServerConnection-->>SmtpClientConnection: Forward or substitute replies
SmtpClientConnection-->>Nodemailer: Deliver SMTP response
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
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. Comment |
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (10)
test/modules/smtp/smtp-auth.test.ts (1)
1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
This setup block (interceptor instance,
beforeAll/afterEach/afterAll,SMTP_PORT) is repeated verbatim across all 8 SMTP test files. See consolidated comment below.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-auth.test.ts` around lines 1 - 19, Extract the repeated SmtpInterceptor setup and SMTP_PORT definition into a shared SMTP test helper, then update each SMTP test file to import and reuse that helper. Preserve the existing beforeAll, afterEach, and afterAll lifecycle behavior while removing the duplicated local boilerplate.test/modules/smtp/smtp-recipient.test.ts (1)
1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-recipient.test.ts` around lines 1 - 19, Remove the duplicated SmtpInterceptor lifecycle setup from this test file and reuse the shared SMTP test setup used by the other SMTP test files. Preserve the existing interceptor initialization, listener cleanup, and disposal behavior through that common setup.test/modules/smtp/smtp-greeting.test.ts (1)
1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-greeting.test.ts` around lines 1 - 19, Extract the shared SmtpInterceptor lifecycle setup currently defined with beforeAll, afterEach, and afterAll into a reusable test helper, then update the SMTP test files to use that helper instead of duplicating the boilerplate. Preserve the existing apply, removeAllListeners, and dispose behavior.test/modules/smtp/smtp-session.test.ts (1)
1-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-session.test.ts` around lines 1 - 21, Remove the duplicated SmtpInterceptor lifecycle setup from this test file and reuse the shared SMTP test setup established for the other SMTP test files. Preserve the existing interceptor initialization, cleanup between tests, and disposal after all tests through that shared helper.test/modules/smtp/smtp-termination.test.ts (1)
1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-termination.test.ts` around lines 1 - 19, Remove the duplicated SmtpInterceptor lifecycle setup from this test file and reuse the shared SMTP test setup used by the other SMTP tests. Preserve the existing interceptor initialization, listener cleanup, and disposal behavior through that shared helper.test/modules/smtp/smtp-errors.test.ts (1)
1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-errors.test.ts` around lines 1 - 19, Remove the duplicated SmtpInterceptor lifecycle setup from this test file and reuse the shared SMTP test setup used by the other SMTP test files. Preserve the existing interceptor initialization, per-test listener cleanup, and final disposal behavior through that shared fixture or helper.test/modules/smtp/smtp-bypass.test.ts (1)
1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-bypass.test.ts` around lines 1 - 19, Remove the duplicated SmtpInterceptor lifecycle setup from this test file by reusing the shared SMTP test setup used across the other SMTP tests. Replace the local interceptor initialization and beforeAll/afterEach/afterAll hooks around SmtpInterceptor with the established shared fixture, while preserving the test’s existing interceptor behavior.test/modules/smtp/smtp-message.test.ts (1)
1-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated interceptor lifecycle boilerplate.
Same setup pattern repeated across all SMTP test files. See consolidated comment below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-message.test.ts` around lines 1 - 20, Consolidate the repeated SmtpInterceptor lifecycle setup currently defined at module scope in the SMTP tests. Reuse the shared setup/teardown helper or fixture for interceptor.apply, removeAllListeners, and dispose, while preserving the existing lifecycle timing and behavior in smtp-message.test.ts.src/interceptors/smtp/index.ts (1)
59-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: avoid the
as anyspread in thesuper()call.
super(...(['session', {}] as any))bypassesTypedEvent's init typing and leaves the basedataunset (other events pass{ data }). Listeners readsession/client/serverdirectly so behavior is fine, but a typedsuper('session', {})(or{ data }) would preserve type checking here and avoid a latent surprise if anything later readsevent.data.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/interceptors/smtp/index.ts` around lines 59 - 66, Update the constructor’s TypedEvent initialization to remove the as any spread and use the typed super call, while preserving the existing session event name and payload. Ensure the base event data is initialized consistently with the other event constructors, without changing the session, client, server, or passthrough assignments.test/modules/smtp/smtp-send.test.ts (1)
21-92: 📐 Maintainability & Code Quality | 🔵 TrivialExtract a shared mock-real-SMTP-server test helper. All three files hand-roll the same buffer-based command/DATA-terminator parsing loop for a fake upstream SMTP server; the shared root cause is the lack of a reusable, parameterized helper (e.g. alongside
createRawTestServerintest/helpers.ts).
test/modules/smtp/smtp-send.test.ts#L21-L92: extract thisSmtpServerLog/createSmtpServer()logic into a shared, configurable helper (queue id, optional command log).test/modules/smtp/smtp-passthrough.test.ts#L26-L84: replace thiscreateSmtpServer()with the shared helper, parameterizing theblocked@example.comrejection behavior.test/modules/smtp/third-party/resend.test.ts#L88-L157: replace thiscreateResendSmtpServer()with the shared helper, parameterizing the AUTH PLAIN credential check.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/modules/smtp/smtp-send.test.ts` around lines 21 - 92, Extract the shared buffer-based SMTP command and DATA parsing from createSmtpServer in test/modules/smtp/smtp-send.test.ts#L21-92 into a reusable parameterized helper alongside createRawTestServer, including queue-id configuration and optional command logging; replace createSmtpServer in test/modules/smtp/smtp-passthrough.test.ts#L26-84 with it while preserving blocked@example.com rejection, and replace createResendSmtpServer in test/modules/smtp/third-party/resend.test.ts#L88-157 while preserving AUTH PLAIN credential validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/interceptors/smtp/index.ts`:
- Around line 59-66: Update the constructor’s TypedEvent initialization to
remove the as any spread and use the typed super call, while preserving the
existing session event name and payload. Ensure the base event data is
initialized consistently with the other event constructors, without changing the
session, client, server, or passthrough assignments.
In `@test/modules/smtp/smtp-auth.test.ts`:
- Around line 1-19: Extract the repeated SmtpInterceptor setup and SMTP_PORT
definition into a shared SMTP test helper, then update each SMTP test file to
import and reuse that helper. Preserve the existing beforeAll, afterEach, and
afterAll lifecycle behavior while removing the duplicated local boilerplate.
In `@test/modules/smtp/smtp-bypass.test.ts`:
- Around line 1-19: Remove the duplicated SmtpInterceptor lifecycle setup from
this test file by reusing the shared SMTP test setup used across the other SMTP
tests. Replace the local interceptor initialization and
beforeAll/afterEach/afterAll hooks around SmtpInterceptor with the established
shared fixture, while preserving the test’s existing interceptor behavior.
In `@test/modules/smtp/smtp-errors.test.ts`:
- Around line 1-19: Remove the duplicated SmtpInterceptor lifecycle setup from
this test file and reuse the shared SMTP test setup used by the other SMTP test
files. Preserve the existing interceptor initialization, per-test listener
cleanup, and final disposal behavior through that shared fixture or helper.
In `@test/modules/smtp/smtp-greeting.test.ts`:
- Around line 1-19: Extract the shared SmtpInterceptor lifecycle setup currently
defined with beforeAll, afterEach, and afterAll into a reusable test helper,
then update the SMTP test files to use that helper instead of duplicating the
boilerplate. Preserve the existing apply, removeAllListeners, and dispose
behavior.
In `@test/modules/smtp/smtp-message.test.ts`:
- Around line 1-20: Consolidate the repeated SmtpInterceptor lifecycle setup
currently defined at module scope in the SMTP tests. Reuse the shared
setup/teardown helper or fixture for interceptor.apply, removeAllListeners, and
dispose, while preserving the existing lifecycle timing and behavior in
smtp-message.test.ts.
In `@test/modules/smtp/smtp-recipient.test.ts`:
- Around line 1-19: Remove the duplicated SmtpInterceptor lifecycle setup from
this test file and reuse the shared SMTP test setup used by the other SMTP test
files. Preserve the existing interceptor initialization, listener cleanup, and
disposal behavior through that common setup.
In `@test/modules/smtp/smtp-send.test.ts`:
- Around line 21-92: Extract the shared buffer-based SMTP command and DATA
parsing from createSmtpServer in test/modules/smtp/smtp-send.test.ts#L21-92 into
a reusable parameterized helper alongside createRawTestServer, including
queue-id configuration and optional command logging; replace createSmtpServer in
test/modules/smtp/smtp-passthrough.test.ts#L26-84 with it while preserving
blocked@example.com rejection, and replace createResendSmtpServer in
test/modules/smtp/third-party/resend.test.ts#L88-157 while preserving AUTH PLAIN
credential validation.
In `@test/modules/smtp/smtp-session.test.ts`:
- Around line 1-21: Remove the duplicated SmtpInterceptor lifecycle setup from
this test file and reuse the shared SMTP test setup established for the other
SMTP test files. Preserve the existing interceptor initialization, cleanup
between tests, and disposal after all tests through that shared helper.
In `@test/modules/smtp/smtp-termination.test.ts`:
- Around line 1-19: Remove the duplicated SmtpInterceptor lifecycle setup from
this test file and reuse the shared SMTP test setup used by the other SMTP
tests. Preserve the existing interceptor initialization, listener cleanup, and
disposal behavior through that shared helper.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 449fa9cc-727b-4495-b16e-e3384663af4c
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
package.jsonsrc/interceptors/net/socket-controller.tssrc/interceptors/smtp/dot-stuffing.tssrc/interceptors/smtp/event-target.tssrc/interceptors/smtp/index.tssrc/interceptors/smtp/smtp-client-connection.tssrc/interceptors/smtp/smtp-server-connection.tssrc/interceptors/smtp/smtp-session.tstest/modules/smtp/smtp-auth.test.tstest/modules/smtp/smtp-bypass.test.tstest/modules/smtp/smtp-errors.test.tstest/modules/smtp/smtp-greeting.test.tstest/modules/smtp/smtp-message.test.tstest/modules/smtp/smtp-passthrough.test.tstest/modules/smtp/smtp-recipient.test.tstest/modules/smtp/smtp-send.test.tstest/modules/smtp/smtp-session.test.tstest/modules/smtp/smtp-termination.test.tstest/modules/smtp/third-party/resend.test.ts
Roadmap
sessionlistener arguments. Too disjoined. Something likesessionmight work nicely.controllerandrealSocketmustn't expose methods like.emit(). They extendEmitterdirectly and those methods leak. Construct the emitter internally, forward only read-only methods (on/once/earlyOn/etc).passthrough()for unhandled sessions opens a stray real connection to the upstream that lingers through a co-active HTTP mock.dns.lookupinterceptor. SMTP usesdns.lookupto lookup hosts and we currently don't capture that at all, which causes non-localhost connections to be invisible to the interceptor.