Skip to content

BEN-390 Author error and testing suite#6

Open
ziptied wants to merge 8 commits into
bentonow:mainfrom
ziptied:main
Open

BEN-390 Author error and testing suite#6
ziptied wants to merge 8 commits into
bentonow:mainfrom
ziptied:main

Conversation

@ziptied

@ziptied ziptied commented Oct 22, 2025

Copy link
Copy Markdown
Contributor

Description

Improved error handling in Bento ActionMailer delivery method
Enhanced response processing with detailed error messages
Added support for parsing JSON error responses
Implemented comprehensive test coverage for BentoActionMailer Railtie and DeliveryMethod

Enhance Bento ActionMailer delivery method with robust error
handling, detailed error messages, and better response parsing.
Add support for parsing JSON error responses, handling
authorization errors, and providing more context for delivery
failures.
…eryMethod

Implement thorough test coverage for the BentoActionMailer Railtie
and DeliveryMethod initialization. The changes include:

- Create test suite for Railtie to validate initializer behavior
- Add tests for delivery method registration
- Implement tests for DeliveryMethod settings initialization
- Ensure idempotent behavior of initializers
- Provide comprehensive test scenarios for configuration options
Refactor test files to use consistent double quotes and
improve code style. Remove unnecessary whitespace and
ensure uniform string representation across test cases.
Modify RuboCop configuration to relax strict rules and increase
development productivity. Disable several style checks and adjust
metrics thresholds to better suit project requirements. Increase
line length limit and exclude test files from certain metrics.
Remove GitHub Actions workflow for PR tests and add
IDE-specific files to gitignore to prevent accidental
tracking of local development environment files
BEN-390
Description

Improved error handling in Bento ActionMailer delivery method
Enhanced response processing with detailed error messages
Added support for parsing JSON error responses
Implemented comprehensive test coverage for BentoActionMailer Railtie and DeliveryMethod
@coderabbitai

coderabbitai Bot commented Oct 22, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a manual GitHub release workflow, IDE ignores and configs, RuboCop changes, extensive DeliveryMethod error handling and a richer DeliveryError API, new type signatures, many new tests and fixtures, and test helper utilities.

Changes

Cohort / File(s) Summary
Release workflow
.github/workflows/manual-release.yml
Adds a manual "Manual Release" workflow with workflow_dispatch inputs version and prerelease; prepares, validates, creates and pushes git tags, and creates GitHub releases (optional prerelease).
Ignore & IDE configs
.gitignore
.idea/...
Adds .idea/ module files and .idea/.gitignore; adds .DS_Store and bench/ to .gitignore.
RuboCop config
.rubocop.yml
Updates RuboCop: disable NewCops, set line length to 140, enforce double-quoted strings, adjust/disable multiple cops, and add metrics constraints with test exclusions.
Docs
AGENTS.md
Adds repository guidelines documenting project layout, build/test commands, coding style, testing practices, commit/PR guidance, and security notes.
Core library
lib/bento_actionmailer.rb
Enhances DeliveryMethod: normalized settings, mail validation (to/from/subject/html), centralized HTTP request/response handling, detailed error parsing/classification, network error wrapping; changes DeliveryError to include response_code and error_details with a custom initializer and readers.
Type signatures
sig/bento/actionmailer.rbs
Adds BentoActionMailer signatures: DeliveryMethod constants, settings accessor, initialize and deliver! signatures; defines DeliveryError with response_code and error_details readers and initializer.
Tests — core & helpers
test/bento/test_actionmailer.rb, test/bento/test_delivery_method.rb, test/bento/test_railtie.rb
Renames main test class; adds comprehensive tests for DeliveryMethod behaviors, response handling, Railtie integration, and many edge cases.
Tests — fixtures & helpers
test/fixtures/api_responses.json, test/fixtures/sample_mail.rb, test/bento/test_helper_extensions.rb, test/test_helper.rb
Adds API response fixtures and Mail fixture builders; introduces Bento::TestHelperExtensions with helpers for building delivery methods, mails, and stubbed HTTP responses; updates test/test_helper.rb to require and include helpers.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "BEN-390 Author error and testing suite" directly captures the two main changes in the changeset: enhanced error handling (specifically for author authorization errors) in the delivery method and the comprehensive test suites added across multiple files. It's specific, concise, and includes the ticket identifier, making it clear to teammates reviewing the history.
Description Check ✅ Passed The description accurately reflects the changeset. It addresses the four core modifications: improved error handling with response_code and error_details in DeliveryError, enhanced response processing via handle_response and parse_error_response methods, JSON error parsing support, and comprehensive test coverage for Railtie and DeliveryMethod. All stated points are present in the actual code changes.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbbcfe0 and 6053188.

📒 Files selected for processing (1)
  • .gitignore (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .gitignore

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (17)
.github/workflows/manual-release.yml (2)

28-29: Remove redundant git fetch --tags step.

Line 26 already enables fetch-tags: true in the checkout action, making this explicit fetch redundant.

-      - name: Fetch tags
-        run: git fetch --tags
-

53-54: Use --local scope for git config in automation.

Scoping git config to the local repository prevents polluting global git configuration when the workflow runs locally for testing or manual debugging.

-          git config user.name "github-actions"
-          git config user.email "github-actions@users.noreply.github.com"
+          git config --local user.name "github-actions"
+          git config --local user.email "github-actions@users.noreply.github.com"
test/test_helper.rb (2)

3-11: Avoid global Warning.warn monkey‑patch; prefer Warning.ignore (fallback only if missing).

Current override is global and may swallow unrelated warnings. Also future Ruby may pass extra args. Gate on Warning.ignore, else fallback with a safer signature.

-module Warning
-  class << self
-    alias_method :original_warn, :warn
-    def warn(msg)
-      return if msg =~ /assigned but unused variable - testEof/
-      original_warn(msg)
-    end
-  end
-end
+if Warning.respond_to?(:ignore)
+  Warning.ignore(/assigned but unused variable - testEof/)
+else
+  module Warning
+    class << self
+      alias_method :original_warn, :warn
+      def warn(*args, **kwargs)
+        msg = args.first.to_s
+        return if msg =~ /assigned but unused variable - testEof/
+        original_warn(*args, **kwargs)
+      end
+    end
+  end
+end

19-21: Redundant double suppression.

You already filter this warning above. Keep one path (ignore vs override), not both.

test/fixtures/api_responses.json (1)

1-23: Fixture looks good.

Covers happy path, 4xx/5xx, and malformed payloads. Consider adding 429 to client_errors for rate‑limit handling tests.

test/bento/test_actionmailer.rb (3)

6-7: Drop local ResponseMock; use shared helper.

test_helper_extensions already provides build_response. Remove the struct to avoid drift.

-class BentoActionMailerTest < Minitest::Test
-  ResponseMock = Struct.new(:code, :body, :message)
+class BentoActionMailerTest < Minitest::Test

8-14: Use build_delivery_method helper for consistency.

Replace manual constructor with build_delivery_method to reuse defaults.

-  def setup
-    @delivery_method = BentoActionMailer::DeliveryMethod.new(
-      site_uuid: "test-uuid",
-      publishable_key: "test-publishable-key",
-      secret_key: "test-secret-key"
-    )
-  end
+  def setup
+    @delivery_method = build_delivery_method(
+      site_uuid: "test-uuid",
+      publishable_key: "test-publishable-key",
+      secret_key: "test-secret-key"
+    )
+  end

161-165: Remove duplicate build_response; rely on test helper.

Avoid redefining the same utility here.

-  private
-
-  def build_response(status, body:, message:)
-    ResponseMock.new(status.to_s, body, message)
-  end
+  # use build_response from Bento::TestHelperExtensions
.rubocop.yml (2)

3-4: Consider enabling new cops.

NewCops: disable can hide useful checks. Suggest enabling and waiving selectively.

-  NewCops: disable
+  NewCops: enable

36-55: Add BlockLength exemption for tests.

Prevents noise on long test contexts.

 Metrics/BlockLength:
   Exclude:
     - 'test/**/*'
lib/bento_actionmailer.rb (2)

80-85: Return trimmed subject.

You validate stripped value, but return the untrimmed one. Return the sanitized value.

-  value = subject.to_s.strip
-  raise DeliveryError, "Mail subject is required" if value.empty?
-  subject.to_s
+  value = subject.to_s.strip
+  raise DeliveryError, "Mail subject is required" if value.empty?
+  value

60-65: Minor: ensure_mail! could validate parts presence sooner.

Optional: also check mail responds to :parts to fail fast for non‑Mail objects.

-  raise DeliveryError, "Mail message is required" unless mail&.respond_to?(:body)
+  raise DeliveryError, "Mail message is required" unless mail&.respond_to?(:body)
+  # Optional hardening:
+  # raise DeliveryError, "Mail message must respond to parts/body" unless mail.respond_to?(:parts) || mail.body.respond_to?(:parts)
test/bento/test_railtie.rb (1)

49-101: Guard constant stubbing against parallel test runs.

These global constant swaps aren’t thread-safe. If the suite ever enables parallelization, run this test serially or wrap setup/teardown in a shared Mutex to avoid cross-test flakiness.

sig/bento/actionmailer.rbs (1)

10-26: Optional: capture response shape in RBS for better tooling.

Typing the HTTP-ish response improves editor checks around error handling. Consider:

 module BentoActionMailer
   class DeliveryMethod
+    type ResponseLike = { code: String, body: String, message: String? }
+
     attr_accessor settings: Hash[Symbol, untyped]
@@
     def initialize: (?Hash[Symbol, untyped]) -> void
     def deliver!: (untyped mail) -> void
+    private def handle_response: (ResponseLike) -> void
test/bento/test_helper_extensions.rb (3)

3-7: Ensure Mail gem is available in test env.

These helpers depend on mail. Add it as a development/test dependency in the gemspec to avoid CI surprises.


75-78: Specify UTF-8 when reading fixtures.

Windows/locale variance can bite JSON parsing. Read with explicit encoding.

-      JSON.parse(File.read(fixture_path))
+      JSON.parse(File.read(fixture_path, mode: "r:UTF-8"))

65-73: Avoid mutable default arg (minor).

Not mutated here, but safer pattern:

-def build_mail_with_custom_parts(to:, from:, subject:, parts: [])
+def build_mail_with_custom_parts(to:, from:, subject:, parts: nil)
   mail = Mail.new
@@
-  parts.each { |part| mail.add_part(part) }
+  Array(parts).each { |part| mail.add_part(part) }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3aff6e8 and dbbcfe0.

⛔ Files ignored due to path filters (1)
  • .DS_Store is excluded by !**/.DS_Store
📒 Files selected for processing (18)
  • .github/workflows/manual-release.yml (1 hunks)
  • .gitignore (1 hunks)
  • .idea/.gitignore (1 hunks)
  • .idea/bento-actionmailer.iml (1 hunks)
  • .idea/modules.xml (1 hunks)
  • .idea/php.xml (1 hunks)
  • .idea/vcs.xml (1 hunks)
  • .rubocop.yml (2 hunks)
  • AGENTS.md (1 hunks)
  • lib/bento_actionmailer.rb (2 hunks)
  • sig/bento/actionmailer.rbs (1 hunks)
  • test/bento/test_actionmailer.rb (1 hunks)
  • test/bento/test_delivery_method.rb (1 hunks)
  • test/bento/test_helper_extensions.rb (1 hunks)
  • test/bento/test_railtie.rb (1 hunks)
  • test/fixtures/api_responses.json (1 hunks)
  • test/fixtures/sample_mail.rb (1 hunks)
  • test/test_helper.rb (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
test/bento/test_helper_extensions.rb (1)
test/bento/test_actionmailer.rb (1)
  • new (5-166)
test/bento/test_delivery_method.rb (2)
test/bento/test_helper_extensions.rb (7)
  • build_delivery_method (19-21)
  • build_mail_message (32-59)
  • build_text_only_mail (61-63)
  • build_mail_stub (84-86)
  • build_mail_with_custom_parts (65-73)
  • fixture_mail (80-82)
  • build_response (23-25)
lib/bento_actionmailer.rb (1)
  • deliver! (38-49)
lib/bento_actionmailer.rb (1)
test/bento/test_actionmailer.rb (1)
  • new (5-166)
🔇 Additional comments (9)
test/test_helper.rb (1)

23-25: LGTM: test helpers mixed into Minitest.

Keeps tests DRY.

test/bento/test_actionmailer.rb (1)

20-159: Broad coverage is solid.

Success path, 401/403 specialization, 4xx/5xx, malformed JSON, and unexpected statuses are all exercised.

test/bento/test_delivery_method.rb (3)

64-83: LGTM: multipart extraction and param mapping verified.

Good capture of send_mail args and address/subject/body plumbing.


221-345: HTTP stubbing is clean and asserts the right things.

Verifies path, headers, payload, host/port/SSL, and error wrapping.


347-412: Utility methods well‑covered.

Success range, error parsing (nil/empty/whitespace/malformed), and DeliveryError shaping all tested.

.rubocop.yml (1)

6-8: Line length bump is fine.

140 suits request specs and JSON bodies.

lib/bento_actionmailer.rb (1)

121-124: Good: network errors mapped to DeliveryError with details.

Covers Timeout, ECONNREFUSED, SocketError, SSL.

test/bento/test_railtie.rb (1)

20-46: Nice coverage of initializer behavior and idempotence.

Assertions exercise inheritance, registration timing, and callback effects cleanly. LGTM.

sig/bento/actionmailer.rbs (1)

1-5: Confirm legacy namespace exposure.

You still declare Bento::Actionmailer::VERSION here. If the runtime no longer defines it, either keep intentionally for compat (document as deprecated) or remove to avoid drift. Please confirm intent.

Comment thread lib/bento_actionmailer.rb
Comment on lines +66 to +71
def extract_html_body(mail)
html_part = Array(mail.body&.parts).find { |part| part.content_type =~ %r{text/html} }
raise DeliveryError, "No HTML body given. Bento requires an html email body." unless html_part

html_part.decoded
end

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 | 🔴 Critical

Bug: HTML part detection is case‑sensitive; fails for 'TEXT/HTML'.

This breaks test_deliver_handles_uppercase_html_content_type. Make the match case‑insensitive (or downcase).

-  html_part = Array(mail.body&.parts).find { |part| part.content_type =~ %r{text/html} }
+  parts = mail.respond_to?(:parts) ? mail.parts : mail.body&.parts
+  html_part = Array(parts).find { |part| part.content_type.to_s =~ %r{text/html}i }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def extract_html_body(mail)
html_part = Array(mail.body&.parts).find { |part| part.content_type =~ %r{text/html} }
raise DeliveryError, "No HTML body given. Bento requires an html email body." unless html_part
html_part.decoded
end
def extract_html_body(mail)
parts = mail.respond_to?(:parts) ? mail.parts : mail.body&.parts
html_part = Array(parts).find { |part| part.content_type.to_s =~ %r{text/html}i }
raise DeliveryError, "No HTML body given. Bento requires an html email body." unless html_part
html_part.decoded
end
🤖 Prompt for AI Agents
In lib/bento_actionmailer.rb around lines 66 to 71, the content_type match is
case-sensitive and will miss values like "TEXT/HTML"; make the detection
case-insensitive by either using a case-insensitive regex (e.g. %r{text/html}i)
or comparing part.content_type&.downcase =~ /text\/html/ so uppercase variants
are matched; also guard against nil content_type by using safe navigation
(part.content_type&.) to avoid exceptions.

Comment on lines +154 to +170
def test_deliver_handles_uppercase_html_content_type
html_part = Mail::Part.new do
content_type "TEXT/HTML; charset=UTF-8"
body "<p>Hi</p>"
end

mail = build_mail_with_custom_parts(
to: "user@example.com",
from: "sender@example.com",
subject: "Caps",
parts: [html_part]
)

@delivery_method.stub(:send_mail, ->(**params) { params[:html_body] }) do
assert_equal "<p>Hi</p>", @delivery_method.deliver!(mail)
end
end

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

Nice edge case: uppercase content type.

This exposes a bug in extract_html_body (case sensitivity). See fix in lib.

🤖 Prompt for AI Agents
In test/bento/test_delivery_method.rb around lines 154 to 170, the test shows
extract_html_body is case-sensitive for content types; update the
extract_html_body implementation in the library to normalize the Mail::Part
content_type before checking for "text/html" (for example call to_s and
downcase, or use Mail::Utilities to parse the media type), handle nil
content_type safely, and use a starts_with / media-type compare rather than
exact case-sensitive equality so parts like "TEXT/HTML; charset=UTF-8" are
recognized as HTML.

Comment on lines +44 to +50
def special_character_mail
multipart_html_mail(
to: "büyer+test@example.com",
from: "✨ sender@example.com",
subject: "Unicode ✓"
)
end

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 | 🟡 Minor

Use a valid From header with display name.

"✨ sender@example.com" isn’t a valid address; prefer display name + address.

-  def special_character_mail
-    multipart_html_mail(
-      to: "büyer+test@example.com",
-      from: "✨ sender@example.com",
-      subject: "Unicode ✓"
-    )
+  def special_character_mail
+    multipart_html_mail(
+      to: "büyer+test@example.com",
+      from: "✨ Sender <sender@example.com>",
+      subject: "Unicode ✓"
+    )
   end
🤖 Prompt for AI Agents
In test/fixtures/sample_mail.rb around lines 44 to 50, the From header uses an
invalid raw email string ("✨ sender@example.com"); change it to a valid
RFC‑style address with a display name and a separate email address (e.g. a
Unicode display name followed by the actual mailbox in angle brackets), ensuring
the mailbox portion is a valid ASCII email and the display name contains the
emoji if desired; update the test fixture to use that display-name <address>
format so the From header validates correctly.

Ignore benchmark-related files and directories to prevent
unintended tracking of performance testing artifacts in
version control
feat: Add bench directory to gitignore
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