Skip to content

Add ForwardEmail sync tests; fix crash when the account has no domains - #12095

Merged
nbudin merged 1 commit into
mainfrom
test-forward-email
Oct 5, 2026
Merged

nbudin merged 1 commit into
mainfrom
test-forward-email

Conversation

@nbudin

@nbudin nbudin commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

What

Tests for the ForwardEmail sync (SyncForwardEmailService and SyncEmailForwardingForDomainJob, both at 0%), which keeps ForwardEmail's aliases in line with our staff positions, event email, and email routes. If this misbehaves, mail is silently dropped or misrouted.

The service builds its own Faraday connection, so the tests wrap Faraday.new to put a fake HTTP adapter behind the service's real middleware (basic auth, form encoding, JSON parsing, raising on HTTP errors). They check what's actually sent over the wire:

  • creating, updating (order-insensitively) and deleting aliases; deleting duplicate aliases; * for a catch-all
  • creating a domain that doesn't exist yet (without a catch-all)
  • pagination of domains and aliases
  • failures: one failed alias change doesn't stop the rest and is reported; one failed domain doesn't stop other domains; a failure listing domains returns a failure from call and raises from call!
  • the API key is sent as the basic auth username
  • the job: passes the right mappings, accepts one or several domains, and does nothing without an API key

Bug found (fixed here)

inner_call returned nil (return if existing_domains.empty?) when ForwardEmail listed no domains. CivilService#call! calls result.exception, so the job died with undefined method 'exception' for nil. It now returns success. Whether bailing out when there are no domains is the right behavior (versus creating them) is a separate question; I kept the existing behavior and just made it not crash.

Noted, not changed

  • Failures changing individual aliases are logged and reported to Sentry/Rollbar, but the sync as a whole still returns success, so the job never fails for them. The test documents this.
  • sync_mappings_for_domain has a return if mappings.nil? that runs after mappings was already used, so it can never trigger.
  • paginate_request builds a URI and query it never uses.

Also adds .rubocop_todo.yml for Lint/MissingSuper on this service (same as the other CivilService classes) and a frozen_string_literal comment, so the file passes the pre-commit rubocop.

🤖 Generated with Claude Code

https://claude.ai/code/session_013mDr1nhgyzST2iPzCzMcCd

SyncForwardEmailService#inner_call returned nil when ForwardEmail listed no
domains, but CivilService#call! expects a result, so
SyncEmailForwardingForDomainJob died with `undefined method 'exception' for
nil`. Return success instead.

Tests run the service against a fake HTTP adapter, with the real middleware in
front of it (auth, form encoding, JSON, raising on errors), so they check what
is actually sent: creating, updating and deleting aliases, duplicate cleanup,
catch-alls, creating missing domains, pagination, partial failures, and
authentication. Also covers the job's API key handling.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013mDr1nhgyzST2iPzCzMcCd
@nbudin nbudin added bug ruby patch Bumps the patch version number on release labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report: Only Changed Files listed

Package Base Coverage New Coverage Difference
app/controllers/application_controller.rb 🟢 76.07% 🟢 80.34% 🟢 4.27%
app/controllers/csv_exports_controller.rb 🔴 47.46% 🟢 100% 🟢 52.54%
app/controllers/graphql_controller.rb 🟢 86.05% 🟢 87.21% 🟢 1.16%
app/controllers/stripe_account_controller.rb 🔴 0% 🟢 100% 🟢 100%
app/controllers/stripe_webhooks_controller.rb 🔴 0% 🟢 100% 🟢 100%
app/graphql/concerns/context_accessors.rb 🟢 90% 🟢 95% 🟢 5%
app/graphql/concerns/order_entry_inputs.rb 🔴 22.22% 🟢 88.89% 🟢 66.67%
app/graphql/concerns/scheduled_value_inputs.rb 🔴 28.57% 🟢 100% 🟢 71.43%
app/graphql/intercode_schema.rb 🟠 61.84% 🟢 85.53% 🟢 23.69%
app/graphql/money_helper.rb 🟠 60% 🟢 100% 🟢 40%
app/graphql/mutations/base_mutation.rb 🟢 81.03% 🟢 89.66% 🟢 8.63%
app/graphql/mutations/create_order.rb 🔴 35.71% 🟢 100% 🟢 64.29%
app/graphql/mutations/create_signup_ranked_choice.rb 🟠 62.5% 🟢 91.67% 🟢 29.17%
app/graphql/mutations/create_signup_request.rb 🟠 57.14% 🟢 90.48% 🟢 33.34%
app/graphql/mutations/create_user_con_profile.rb 🔴 32% 🟢 100% 🟢 68%
app/graphql/mutations/create_user_signup.rb 🟠 51.72% 🟢 93.1% 🟢 41.38%
app/graphql/mutations/merge_users.rb 🟠 50% 🟢 100% 🟢 50%
app/graphql/mutations/submit_order.rb 🟠 52.63% 🟢 100% 🟢 47.37%
app/graphql/mutations/update_convention.rb 🔴 30.43% 🟢 100% 🟢 69.57%
app/graphql/mutations/withdraw_user_signup.rb 🟠 60% 🟢 100% 🟢 40%
app/graphql/sources/signup_choice.rb 🔴 0% 🟢 100% 🟢 100%
app/graphql/sources/simulated_skip_reason.rb 🔴 0% 🟢 100% 🟢 100%
app/graphql/sources/waitlist_position.rb 🔴 0% 🟢 100% 🟢 100%
app/graphql/types/date_type.rb 🟠 71.43% 🟢 100% 🟢 28.57%
app/graphql/types/json.rb 🟢 77.78% 🟢 88.89% 🟢 11.11%
app/javascript/Authentication/AccountFormContent.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Authentication/DeviseForgotPasswordPage.tsx 🔴 0% 🟢 85.19% 🟢 85.19%
app/javascript/Authentication/EditUser.tsx 🔴 0% 🟢 86.05% 🟢 86.05%
app/javascript/Authentication/PasswordConfirmationInput.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Authentication/PasswordInputWithStrengthCheck.tsx 🔴 0% 🟢 82.35% 🟢 82.35%
app/javascript/Authentication/ResetPassword.tsx 🔴 0% 🟢 96% 🟢 96%
app/javascript/Authentication/SignInButton.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Authentication/UserFormFields.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Authentication/authenticationManager.ts 🔴 16.33% 🟢 100% 🟢 83.67%
app/javascript/Authentication/queries.generated.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/Authentication/useSignInContext.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/BuiltInForms/FileUploadForm.tsx 🔴 0% 🟢 80% 🟢 80%
app/javascript/CmsAdmin/CmsFilesAdmin/FileInputWithPreview.tsx 🔴 45.45% 🟢 86.36% 🟢 40.91%
app/javascript/CmsAdmin/CmsFilesAdmin/FilePreview.tsx 🔴 0% 🔴 44.44% 🟢 44.44%
app/javascript/CmsAdmin/CmsPartialsAdmin/mutations.generated.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/EventsApp/EventPage/RunCapacityGraphBucket.tsx 🟠 66.67% 🟠 72.22% 🟢 5.55%
app/javascript/EventsApp/EventPage/buildSignupOptions.ts 🟢 88.89% 🟢 90.48% 🟢 1.59%
app/javascript/EventsApp/ScheduleGrid/Schedule.ts 🔴 0% 🟢 94.49% 🟢 94.49%
app/javascript/EventsApp/ScheduleGrid/ScheduleGridConfig.ts 🔴 0% 🟢 86.67% 🟢 86.67%
app/javascript/EventsApp/ScheduleGrid/ScheduleGridLayout.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/EventsApp/ScheduleGrid/ScheduleLayout/ColumnReservation.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/EventsApp/ScheduleGrid/ScheduleLayout/ColumnReservationSet.ts 🔴 0% 🟢 92.06% 🟢 92.06%
app/javascript/EventsApp/ScheduleGrid/ScheduleLayout/ScheduleLayoutBlock.ts 🔴 0% 🟢 84.95% 🟢 84.95%
app/javascript/FormAdmin/FormItemUtils.ts 🔴 24% 🟢 95% 🟢 71%
app/javascript/FormPresenter/ItemInputs/RequiredIndicator.tsx 🟠 66.67% 🟢 100% 🟢 33.33%
app/javascript/FormPresenter/ItemInteractionTracker.tsx 🟢 90.91% 🟢 100% 🟢 9.09%
app/javascript/FormPresenter/Layouts/FormBody.tsx 🟠 57.58% 🟠 60.61% 🟢 3.03%
app/javascript/Models/deserializeFormResponse.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/RegistrationPolicy/PreventNoPreferenceSignupRow.tsx 🟠 75% 🟢 91.67% 🟢 16.67%
app/javascript/RegistrationPolicy/RegistrationPolicy.ts 🟠 72.73% 🟢 81.82% 🟢 9.09%
app/javascript/RegistrationPolicy/RegistrationPolicyBucket.ts 🟠 66.67% 🟢 93.33% 🟢 26.66%
app/javascript/ScheduledValueUtils.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/ApplyCouponControl.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/Cart/CartContents.tsx 🔴 0% 🟢 93.75% 🟢 93.75%
app/javascript/Store/Cart/coupon_applications/$id.ts 🔴 0% 🟠 75% 🟢 75%
app/javascript/Store/Cart/coupon_applications/route.ts 🔴 0% 🟢 91.67% 🟢 91.67%
app/javascript/Store/Cart/index.tsx 🔴 0% 🟢 95.56% 🟢 95.56%
app/javascript/Store/Cart/order_entries/$id.ts 🔴 0% 🟢 92.86% 🟢 92.86%
app/javascript/Store/MoneyInput.tsx 🔴 3.45% 🟠 62.07% 🟢 58.62%
app/javascript/Store/OrderHistory.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/OrderPaymentModal.tsx 🔴 0% 🟢 97.06% 🟢 97.06%
app/javascript/Store/OrderSummary.tsx 🔴 0% 🟢 97.37% 🟢 97.37%
app/javascript/Store/ProductOrderForm.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/ProductPage.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/buildMoneyInput.ts 🔴 0% 🟠 66.67% 🟢 66.67%
app/javascript/Store/describeCoupon.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/describePricingStructure.tsx 🔴 0% 🟢 90.77% 🟢 90.77%
app/javascript/Store/mutations.generated.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/Store/sortProductVariants.ts 🔴 9.09% 🔴 45.45% 🟢 36.36%
app/javascript/Store/useSubmitOrder.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/TimeUtils.ts 🟠 69.81% 🟠 71.7% 🟢 1.89%
app/javascript/Timespan.ts 🟢 85.37% 🟢 87.8% 🟢 2.43%
app/javascript/UIComponents/BlockPartial.tsx 🔴 0% 🔴 44.12% 🟢 44.12%
app/javascript/UserConProfiles/EditUserConProfile.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/UserConProfiles/UserConProfileForm.tsx 🔴 0% 🟢 100% 🟢 100%
app/javascript/UserConProfiles/buildFormStateFromData.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/UserConProfiles/mutations.generated.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/UserConProfiles/queries.generated.ts 🔴 0% 🟢 100% 🟢 100%
app/javascript/formatMoney.ts 🔴 0% 🟢 88.89% 🟢 88.89%
app/javascript/parseCmsContent.tsx 🔴 40% 🔴 44% 🟢 4%
app/javascript/parsePageContent.tsx 🔴 4.42% 🟢 94.69% 🟢 90.27%
app/javascript/setupI18Next.ts 🟠 65.52% 🟢 79.31% 🟢 13.79%
app/javascript/useAsyncFunction.ts 🟢 93.75% 🟢 94.74% 🟢 0.99%
app/jobs/sync_email_forwarding_for_domain_job.rb 🟢 80% 🟢 100% 🟢 20%
app/liquid_drops/bucket_availability_drop.rb 🔴 0% 🟢 77.78% 🟢 77.78%
app/liquid_drops/run_availability_drop.rb 🔴 0% 🟠 70.59% 🟢 70.59%
app/models/concerns/names.rb 🟠 62.5% 🟢 93.75% 🟢 31.25%
app/models/coupon.rb 🟠 66.67% 🟠 70.37% 🟢 3.7%
app/models/email_route.rb 🟢 79.31% 🟢 93.1% 🟢 13.79%
app/models/event.rb 🟢 80.41% 🟢 85.57% 🟢 5.16%
app/models/form_item.rb 🟢 90.91% 🟢 94.55% 🟢 3.64%
app/models/registration_policy.rb 🟢 98.7% 🟢 98.72% 🟢 0.02%
app/models/signup_request.rb 🟢 95.45% 🟢 96.3% 🟢 0.85%
app/models/user.rb 🟢 92.59% 🟢 96.3% 🟢 3.71%
app/models/user_con_profile.rb 🟠 68.14% 🟠 70.8% 🟢 2.66%
app/notifiers/orders/cancelled_notifier.rb 🟢 86.67% 🟢 93.33% 🟢 6.66%
app/policies/application_policy.rb 🟢 93.94% 🟢 94.74% 🟢 0.8%
app/policies/coupon_application_policy.rb 🔴 0% 🟢 100% 🟢 100%
app/policies/coupon_policy.rb 🔴 0% 🟢 100% 🟢 100%
app/policies/department_policy.rb 🟠 50% 🟢 88.89% 🟢 38.89%
app/policies/email_route_policy.rb 🟠 66.67% 🟢 100% 🟢 33.33%
app/policies/event_rating_policy.rb 🟢 78.57% 🟢 100% 🟢 21.43%
app/policies/notification_template_policy.rb 🟠 50% 🟢 100% 🟢 50%
app/policies/order_entry_policy.rb 🟢 80% 🟢 100% 🟢 20%
app/policies/order_policy.rb 🟢 94.87% 🟢 97.37% 🟢 2.5%
app/policies/ranked_choice_decision_policy.rb 🔴 0% 🟢 100% 🟢 100%
app/policies/ranked_choice_user_constraint_policy.rb 🔴 0% 🟢 100% 🟢 100%
app/policies/signup_change_policy.rb 🟠 66.67% 🟢 100% 🟢 33.33%
app/policies/signup_ranked_choice_policy.rb 🔴 0% 🟢 100% 🟢 100%
app/policies/signup_round_policy.rb 🔴 0% 🟢 100% 🟢 100%
app/policies/user_activity_alert_policy.rb 🟢 77.78% 🟢 100% 🟢 22.22%
app/policies/user_con_profile_policy.rb 🟢 91.55% 🟢 100% 🟢 8.45%
app/presenters/form_response_presenter.rb 🟢 88.46% 🟢 94.23% 🟢 5.77%
app/presenters/run_availability_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/conventions_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/coupons_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/email_routes_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/event_proposals_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/events_table_results_presenter.rb 🟠 57.63% 🟢 100% 🟢 42.37%
app/presenters/tables/form_items.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/orders_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/ranked_choice_decisions_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/runs_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/signup_changes_table_results_presenter.rb 🟢 79.31% 🟢 100% 🟢 20.69%
app/presenters/tables/signup_requests_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/signups_table_results_presenter.rb 🟠 67.16% 🟢 100% 🟢 32.84%
app/presenters/tables/table_results_presenter.rb 🟢 77.88% 🟢 100% 🟢 22.12%
app/presenters/tables/user_con_profiles_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/presenters/tables/users_table_results_presenter.rb 🔴 0% 🟢 100% 🟢 100%
app/services/cancel_order_service.rb 🔴 0% 🟢 100% 🟢 100%
app/services/concerns/split_ticket_order.rb 🔴 0% 🟢 100% 🟢 100%
app/services/connect_stripe_account_service.rb 🔴 0% 🟢 100% 🟢 100%
app/services/delete_ticket_service.rb 🔴 0% 🟢 96.15% 🟢 96.15%
app/services/email_forwarding_router.rb 🟠 58.42% 🟠 59.41% 🟢 0.99%
app/services/merge_users_service.rb 🟢 96.25% 🟢 100% 🟢 3.75%
app/services/submit_order_service.rb 🟢 87.1% 🟢 93.55% 🟢 6.45%
app/services/sync_forward_email_service.rb 🔴 0% 🟢 92.86% 🟢 92.86%
Overall Coverage 🟢 58.35% 🟢 52.38% 🔴 -5.97%

Minimum allowed coverage is 0%, this run produced 52.38%

@nbudin
nbudin merged commit 1f3ed72 into main Oct 5, 2026
25 checks passed
@nbudin
nbudin deleted the test-forward-email branch October 5, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug patch Bumps the patch version number on release ruby

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant