Skip to content

Conversation

@jurgens
Copy link

@jurgens jurgens commented Aug 5, 2025

Motivation

add API support for projects

Changes

  • add projects API wrapper and project struct
  • add specs and rubydocs

Summary by CodeRabbit

  • New Features

    • Project management added: list, retrieve, create, update, and delete projects via the API.
  • Tests

    • Comprehensive test coverage and recorded HTTP fixtures for project operations, including success and error scenarios.
  • Chores

    • Repository ignore list updated to exclude a new developer file (.claude).

@coderabbitai
Copy link

coderabbitai bot commented Aug 5, 2025

Walkthrough

A new ProjectsAPI class and Project struct were added for project CRUD operations. Tests and many VCR fixtures were added/updated. Spec helper VCR redaction logic was changed. Client initialization no longer auto-selects an api_host. A .claude entry was added to .gitignore.

Changes

Cohort / File(s) Summary
Core Projects API
lib/mailtrap.rb, lib/mailtrap/project.rb, lib/mailtrap/projects_api.rb
Added Mailtrap::Project struct with to_h and introduced Mailtrap::ProjectsAPI with list, get, create, update, delete, base_path and wrap_request. lib/mailtrap.rb now requires the new API file.
Client
lib/mailtrap/client.rb
Removed automatic api_host selection (`api_host
VCR / Test infra
spec/spec_helper.rb, .gitignore
Reworked VCR filtering: removed some account_id redactions, added config.filter_sensitive_data('ACCOUNT_ID', ENV.fetch('MAILTRAP_ACCOUNT_ID')), and added a before_record hook to redact share_links in responses. Added .claude to .gitignore.
Projects API tests
spec/mailtrap/project_spec.rb, spec/mailtrap/projects_api_spec.rb
Added RSpec tests for Mailtrap::Project and full CRUD coverage for Mailtrap::ProjectsAPI, including success and error scenarios (auth, not-found).
Projects API fixtures
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/*
Added many VCR cassettes covering list/get/create/update/delete flows and error cases (401, 404, 422), plus variants (hash requests, name-only updates, delete return).
EmailTemplates fixtures (refreshed)
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/*
Updated numerous EmailTemplates VCR cassettes: replaced hard-coded account IDs with ACCOUNT_ID, refreshed timestamps, headers (Date, Cf‑Ray/Server), CSP nonces and related metadata; no code/API signature changes.
Other fixtures updated
spec/fixtures/vcr_cassettes/Mailtrap_SuppressionsAPI/...
Updated recorded interaction(s) to use ACCOUNT_ID placeholder and refreshed request/response metadata (timestamps, headers, CSP nonce).

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,230,201,0.6)
    participant Test
    participant ProjectsAPI as Projects API
    participant BaseAPI as Base API
    participant Server as Mailtrap API
    end

    Test->>ProjectsAPI: list()
    ProjectsAPI->>BaseAPI: base_list(path: /api/accounts/{account_id}/projects)
    BaseAPI->>Server: GET /api/accounts/{id}/projects
    Server-->>BaseAPI: 200 [projects JSON]
    BaseAPI->>ProjectsAPI: [mapped Project objects]
    ProjectsAPI-->>Test: [Mailtrap::Project[]]

    Test->>ProjectsAPI: create({name: "New Project"})
    ProjectsAPI->>ProjectsAPI: wrap_request(params) -> { project: {...} }
    ProjectsAPI->>BaseAPI: base_create(body: { project: {...} })
    BaseAPI->>Server: POST /api/accounts/{id}/projects
    Server-->>BaseAPI: 200/201 [project JSON]
    BaseAPI->>ProjectsAPI: Project object
    ProjectsAPI-->>Test: Mailtrap::Project
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to the client initialization change in lib/mailtrap/client.rb (behavioral risk if callers expect auto-selection of api_host).
  • Verify spec/spec_helper.rb VCR before_record redaction correctly handles both Hash and Array response bodies and does not mangle unrelated fixtures.
  • Confirm ProjectsAPI#wrap_request and base_path align with BaseAPI conventions (payload wrapping and path construction).
  • Large numbers of regenerated VCR fixtures (EmailTemplates, ProjectsAPI) should be scanned to ensure only intended changes (placeholders, timestamps, CSP/header updates) are present.

Suggested reviewers

  • IgorDobryn
  • VladimirTaytor

Poem

🐰 I hopped in quick with struct and route,
CRUD carrots ready, no need to shout.
Tests recorded, share links tucked away,
Client kept tidy — hop, hop, hooray!
The warren’s cleaner for another day. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a projects API, which is the primary objective of this pull request.
Description check ✅ Passed The PR description provides motivation and lists key changes, but omits the 'How to test' and 'Images and GIFs' sections from the template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

🧹 Nitpick comments (5)
lib/mailtrap/project.rb (3)

6-6: Fix documentation comment.

The documentation refers to "contact ID" but should reference "project ID" since this is a Project struct.

-  # @attr_reader id [String] The contact ID
+  # @attr_reader id [String] The project ID

24-27: Fix documentation comment and approve implementation.

The documentation refers to "contact" but should reference "project". The implementation logic is correct.

-    # @return [Boolean] Whether the contact was newly created
+    # @return [Boolean] Whether the project was newly created

29-32: Fix documentation comment and approve implementation.

The documentation refers to "contact attributes" but should reference "project attributes". The implementation using super.compact is correct for removing nil values.

-    # @return [Hash] The contact attributes as a hash
+    # @return [Hash] The project attributes as a hash
spec/mailtrap/projects_api_spec.rb (2)

80-93: Remove duplicate test context.

The "with hash request" context duplicates the main create test with identical request data and expectations. Consider removing this redundant test case.

-    context 'with hash request' do
-      let(:request) do
-        {
-          name: 'New Project'
-        }
-      end
-
-      it 'maps response data to Project object' do
-        expect(create).to be_a(Mailtrap::Project)
-        expect(create).to have_attributes(
-          name: 'New Project'
-        )
-      end
-    end

133-146: Remove duplicate test context.

Similar to the create method, this "with hash request" context duplicates the main update test. Consider removing this redundant test case.

-    context 'with hash request' do
-      let(:request) do
-        {
-          name: 'Updated Project'
-        }
-      end
-
-      it 'maps response data to Project object' do
-        expect(update).to be_a(Mailtrap::Project)
-        expect(update).to have_attributes(
-          name: 'Updated Project'
-        )
-      end
-    end
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 022fa3b and 0c1090c.

📒 Files selected for processing (7)
  • lib/mailtrap.rb (1 hunks)
  • lib/mailtrap/project.rb (1 hunks)
  • lib/mailtrap/projects_api.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1 hunks)
  • spec/mailtrap/project_spec.rb (1 hunks)
  • spec/mailtrap/projects_api_spec.rb (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
spec/mailtrap/project_spec.rb (1)
lib/mailtrap/project.rb (2)
  • newly_created? (25-27)
  • to_h (30-32)
lib/mailtrap/projects_api.rb (1)
lib/mailtrap/base_api.rb (7)
  • supported_options (27-29)
  • response_class (31-33)
  • base_list (67-70)
  • base_get (46-49)
  • base_create (51-55)
  • base_update (57-61)
  • base_delete (63-65)
🔇 Additional comments (14)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1)

1-167: LGTM!

This VCR cassette properly captures a 401 Unauthorized response scenario for testing API error handling. The structure and content are appropriate for testing the ProjectsAPI authentication flow.

lib/mailtrap/project.rb (2)

11-18: LGTM!

The struct definition with keyword initialization is well-structured and follows Ruby conventions.


19-22: LGTM!

The custom initialize method properly extracts the action parameter before calling the parent constructor. This approach allows the struct to track creation/update state while maintaining the struct's functionality.

lib/mailtrap.rb (1)

11-11: LGTM!

The require statement is properly placed and follows the established pattern for integrating API modules into the main library.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1)

1-167: No duplication detected—fixtures cover distinct scenarios
Both VCR cassettes exercise different responses (one maps project data; the other returns a 401 error with {"error":"Incorrect API token"}), and the header differences you saw (nonces, request IDs, runtimes, etc.) are expected dynamic values. These files serve unique test purposes, so this duplication concern can be closed.

spec/mailtrap/project_spec.rb (3)

3-60: LGTM!

The initialization tests provide excellent coverage of the Project struct, including complex nested data structures for share_links, inboxes, and permissions. The test data is realistic and comprehensive.


62-105: LGTM!

The newly_created? method tests cover all relevant scenarios:

  • Action is 'created' → returns true
  • Action is 'updated' → returns false
  • Action is nil → returns true

This thoroughly validates the method's logic from lib/mailtrap/project.rb.


107-183: LGTM!

The to_h method tests comprehensively verify:

  • Hash conversion with all attributes present
  • Proper handling of nil values (compact behavior)

This validates the super.compact implementation in the Project struct.

spec/mailtrap/projects_api_spec.rb (4)

1-8: LGTM!

Good use of frozen string literal and proper RSpec setup with VCR integration. The environment variable fallbacks provide flexibility for local testing.


9-31: Comprehensive test coverage for the list method.

Good testing of both successful response mapping and authorization error handling. The error assertions properly validate error type, message content, and the messages array.


33-62: Well-structured get method tests.

The test appropriately creates a project dependency for the positive test case and properly validates both successful retrieval and not found error scenarios.


172-197: Proper delete method testing.

Good coverage of both successful deletion (returning nil) and not found error scenarios. The test structure is consistent with other method tests.

lib/mailtrap/projects_api.rb (2)

1-13: Proper API class setup.

Good use of frozen string literal, appropriate dependencies, and correct BaseAPI integration. The supported options and response class are properly configured.


57-66: Private methods correctly implemented.

The base_path constructs the appropriate API endpoint and wrap_request properly wraps options in the expected project key format.

@yanchuk yanchuk requested review from IgorDobryn and i7an August 5, 2025 14:20
@yanchuk yanchuk linked an issue Aug 5, 2025 that may be closed by this pull request

interaction.request.uri.gsub!(%r{/accounts/\d+/}, '/accounts/1111111/')
interaction.response.body.gsub!(/"account_id":\d+/, '"account_id": 1111111')
# interaction.request.uri.gsub!(%r{/accounts/\d+/}, '/accounts/1111111/')
Copy link
Author

Choose a reason for hiding this comment

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

@IgorDobryn @i7an I had to remove this in order for projects spec to run - otherwise VCR was not recognising cassettes (due to changed URLs) and was failing

there are also failing email templates specs - I had rerun and re-record cassettes. I'm not committing these changes though so I wanted to know your opinion if this account ID removal is absolutely necessary or not.

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

♻️ Duplicate comments (1)
lib/mailtrap/project.rb (1)

19-21: Remove redundant initializer.

This override adds no behavior and can be dropped to rely on Struct’s keyword_init initializer. Keeps the DTO leaner and avoids future confusion.

-    def initialize(options)
-      super
-    end
🧹 Nitpick comments (3)
lib/mailtrap/project.rb (1)

24-26: to_h looks good; minor compatibility check.

Hash#compact requires Ruby >= 2.4 (generally fine). If this gem supports very old Rubies, replace with reject { |_, v| v.nil? }.

-      super.compact
+      super.reject { |_, v| v.nil? }

Would you like me to scan the repo for required_ruby_version in the gemspec to confirm compatibility?

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1)

5-5: Account ID is stored in the cassette URI; consider normalizing.

The URI includes the real account_id. If this repo is public or shared broadly, normalize account_id during recording (see spec_helper suggestion to add a URI matcher and normalization) to avoid leaking internal identifiers.

I can provide a one-off script to rewrite existing cassettes to replace /accounts// with /accounts/<ACCOUNT_ID>/ if you decide to normalize retroactively. Want me to generate it?

spec/mailtrap/projects_api_spec.rb (1)

178-181: Align example description with the actual expectation

The example says “returns no content” but asserts a payload { id: project_id }. Update the description for clarity and to avoid confusion.

Apply this diff:

-    it 'returns no content' do
+    it 'returns deleted project id' do
       expect(delete).to eq({ id: project_id })
     end
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cb42fc5 and a2b6be6.

📒 Files selected for processing (17)
  • lib/mailtrap/project.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_no_content.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/mailtrap/project_spec.rb (1 hunks)
  • spec/mailtrap/projects_api_spec.rb (1 hunks)
  • spec/spec_helper.rb (1 hunks)
✅ Files skipped from review due to trivial changes (9)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_no_content.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/mailtrap/project_spec.rb
🧰 Additional context used
🧬 Code Graph Analysis (1)
spec/mailtrap/projects_api_spec.rb (1)
lib/mailtrap/projects_api.rb (6)
  • list (17-19)
  • include (7-66)
  • get (25-27)
  • create (35-37)
  • update (45-47)
  • delete (53-55)
🔇 Additional comments (5)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml (1)

164-165: Critical: Sanitize share_links in VCR fixtures

The recorded cassettes still expose real admin/viewer share‐link tokens. You must redact all share_links URLs in your fixtures to avoid leaking sensitive tokens.

• Target fixtures (e.g. spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml, lines 164–165) and any other files under spec/fixtures/vcr_cassettes containing "share_links".
• Bulk‐redact in place:

find spec/fixtures/vcr_cassettes -name '*.yml' \
  -exec sed -i -E "s#https://mailtrap\.io/projects/[0-9]+/share/[^\"']+#<REDACTED_SHARE_LINK>#g" {} +

• (Optionally) Add VCR filters in spec/spec_helper.rb to scrub during recording:

VCR.configure do |c|
  c.filter_sensitive_data('<ADMIN_SHARE_LINK>') do |i|
    JSON.parse(i.response.body)['share_links']['admin']
  end
  c.filter_sensitive_data('<VIEWER_SHARE_LINK>') do |i|
    JSON.parse(i.response.body)['share_links']['viewer']
  end
end

• Verify redaction with:

rg -nP '<REDACTED_SHARE_LINK>' spec/fixtures/vcr_cassettes || echo "Redaction check failed!"
spec/mailtrap/projects_api_spec.rb (4)

12-15: LGTM: list maps to DTOs correctly

The expectation focuses on type mapping, which is sufficient here and avoids brittle attribute assertions.


19-26: LGTM: authorization failure path is well asserted

Good coverage of error class, message, and aggregated messages.


32-45: LGTM: happy-path and error-path coverage is solid

Good coverage of:

  • get: mapping and 404
  • create: mapping and client error
  • update: not-found path
  • delete: not-found path

Assertions check both the error type and message text.

Also applies to: 91-104, 155-166, 182-193


61-61: Be explicit about kwargs vs Hash to avoid Ruby 3 keyword separation pitfalls

The ProjectsAPI methods are defined as:

def create(options)
  base_create(options)
end

def update(project_id, options)
  base_update(project_id, options)
end

but your specs call:

project.create(**request)
project.update(project_id, **request)

– under Ruby 2.7 this emits a warning and still passes the Hash, but under Ruby 3.x it will raise an “unknown keyword” error. You have two paths:

Option A (tests-only)
Keep the existing API signatures and change the spec subjects to pass the Hash positionally:

- subject(:create) { project.create(**request) }
+ subject(:create) { project.create(request) }

- subject(:update) { project.update(project_id, **request) }
+ subject(:update) { project.update(project_id, request) }

Option B (library)
Accept keyword args in the API itself:

def create(**options)
  base_create(options)
end

def update(project_id, **options)
  base_update(project_id, options)
end

At minimum, please verify which Ruby version your library targets (e.g. via Gemfile, CI config or .ruby-version) and ensure your method signatures and call sites align.

Affected specs:

  • spec/mailtrap/projects_api_spec.rb at lines 61 and 108

Comment on lines 6 to 10
# @attr_reader id [String] The project ID
# @attr_reader name [String] The project name
# @attr_reader share_links [Array] Array of links
# @attr_reader inboxes [Array] Array of inboxes
# @attr_reader permissions [Hash] List of permissions
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix YARD types and accessor annotations (docs are incorrect).

  • id is returned as an Integer by the API, not String.
  • share_links is a Hash (with admin/viewer), not an Array.
  • Struct defines writer methods too; documenting these as reader-only is misleading.

Apply this doc fix:

-  # @attr_reader id [String] The project ID
-  # @attr_reader name [String] The project name
-  # @attr_reader share_links [Array] Array of links
-  # @attr_reader inboxes [Array] Array of inboxes
-  # @attr_reader permissions [Hash] List of permissions
+  # @!attribute [rw] id
+  #   @return [Integer] The project ID
+  # @!attribute [rw] name
+  #   @return [String] The project name
+  # @!attribute [rw] share_links
+  #   @return [Hash] Admin and viewer share links
+  # @!attribute [rw] inboxes
+  #   @return [Array] Array of inboxes
+  # @!attribute [rw] permissions
+  #   @return [Hash] List of permissions
📝 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
# @attr_reader id [String] The project ID
# @attr_reader name [String] The project name
# @attr_reader share_links [Array] Array of links
# @attr_reader inboxes [Array] Array of inboxes
# @attr_reader permissions [Hash] List of permissions
# @!attribute [rw] id
# @return [Integer] The project ID
# @!attribute [rw] name
# @return [String] The project name
# @!attribute [rw] share_links
# @return [Hash] Admin and viewer share links
# @!attribute [rw] inboxes
# @return [Array] Array of inboxes
# @!attribute [rw] permissions
# @return [Hash] List of permissions
🤖 Prompt for AI Agents
In lib/mailtrap/project.rb around lines 6 to 10, the YARD annotations are
incorrect: change the id type to Integer instead of String, change share_links
to a Hash (e.g. Hash with admin/viewer keys) instead of Array, keep inboxes as
an Array and permissions as a Hash, and mark the attributes as read-write (the
Struct defines writers) rather than reader-only; update the @attr_* tags or
replace them with @!attribute [rw] annotations to reflect the correct types and
that getters and setters exist.

Comment on lines 3 to 8
RSpec.describe Mailtrap::ProjectsAPI, :vcr do
subject(:project) { described_class.new(account_id, client) }

let(:account_id) { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_API_KEY')) }

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Double-check VCR cassettes don’t leak secrets/account_id

This spec depends on ENV variables and uses VCR. Given spec_helper recently commented out filters for account_id, ensure cassettes don’t contain live secrets or unredacted account identifiers.

Run the following scan to spot potential leaks:

If we find leaks, I can help re-add VCR filters and re-record cassettes with redaction.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Scanning cassettes for obvious secrets..."
# Authorization headers (common patterns)
rg -n -C1 -g 'spec/fixtures/vcr_cassettes/**' -P '(Authorization:|Api-Token:|X-Api-Token:|Bearer\s+[A-Za-z0-9\.\-_]+)'

echo -e "\nScanning for account id patterns (6+ digits) in URLs and bodies..."
rg -n -C1 -g 'spec/fixtures/vcr_cassettes/**' -P '/api/accounts/\d{5,}/projects'

echo -e "\nScanning for raw API keys from env names..."
rg -n -C1 -g 'spec/fixtures/vcr_cassettes/**' -P '(MAILTRAP_API_KEY|MAILTRAP_ACCOUNT_ID)'

Length of output: 47012


Leaked Account ID in VCR Cassettes

Our scan shows hard-coded account IDs (e.g. 1495731) in the URIs of all spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/** files. We need to redact these before merging.

• In your spec_helper.rb (or VCR setup), re-enable and add:

VCR.configure do |config|
  # …existing filters…
  config.filter_sensitive_data('<MAILTRAP_ACCOUNT_ID>') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
end

• Re-record the Project API cassettes so the real ID is replaced by the placeholder.

Let me know if you’d like assistance with updating and re-recording these cassettes.

🤖 Prompt for AI Agents
In spec/mailtrap/projects_api_spec.rb lines 3-8 there are tests that caused VCR
cassettes to record a real Mailtrap account ID; update your VCR configuration
(e.g., in spec/spec_helper.rb or the existing VCR setup) to add a
filter_sensitive_data entry that replaces ENV.fetch('MAILTRAP_ACCOUNT_ID') with
a placeholder like <MAILTRAP_ACCOUNT_ID>, then re-record the
Mailtrap_ProjectsAPI cassettes (run the specs that exercise ProjectsAPI with VCR
enabled) so the recorded URIs use the placeholder instead of the real ID;
finally commit the updated spec helper and the re-recorded cassettes.

Comment on lines +76 to +89
context 'with hash request' do
let(:request) do
{
name: 'New Project'
}
end

it 'maps response data to Project object' do
expect(create).to be_a(Mailtrap::Project)
expect(create).to have_attributes(
name: 'New Project'
)
end
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

“with hash request” doesn’t change the call shape; override subject to pass a Hash

Right now, the context still calls project.create(**request) via the outer subject, so it exercises the same kwargs path. Override subject here to actually pass a positional Hash.

Apply this diff:

 context 'with hash request' do
+  subject(:create) { project.create(request) }
   let(:request) do
     {
       name: 'New Project'
     }
   end
📝 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
context 'with hash request' do
let(:request) do
{
name: 'New Project'
}
end
it 'maps response data to Project object' do
expect(create).to be_a(Mailtrap::Project)
expect(create).to have_attributes(
name: 'New Project'
)
end
end
context 'with hash request' do
subject(:create) { project.create(request) }
let(:request) do
{
name: 'New Project'
}
end
it 'maps response data to Project object' do
expect(create).to be_a(Mailtrap::Project)
expect(create).to have_attributes(
name: 'New Project'
)
end
end
🤖 Prompt for AI Agents
In spec/mailtrap/projects_api_spec.rb around lines 76 to 89, the "with hash
request" context still uses the outer subject which calls
project.create(**request) (keyword args) so it doesn't test the positional-Hash
path; override the subject inside this context to call project.create(request)
(positional Hash) so the spec exercises the Hash call shape instead of kwargs.

Comment on lines +129 to +142
context 'with hash request' do
let(:request) do
{
name: 'Updated Project'
}
end

it 'maps response data to Project object' do
expect(update).to be_a(Mailtrap::Project)
expect(update).to have_attributes(
name: 'Updated Project'
)
end
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Same issue for update: ensure the hash path is actually tested

Override subject in this context to pass a positional Hash instead of kwargs.

Apply this diff:

 context 'with hash request' do
+  subject(:update) { project.update(project_id, request) }
   let(:request) do
     {
       name: 'Updated Project'
     }
   end
📝 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
context 'with hash request' do
let(:request) do
{
name: 'Updated Project'
}
end
it 'maps response data to Project object' do
expect(update).to be_a(Mailtrap::Project)
expect(update).to have_attributes(
name: 'Updated Project'
)
end
end
context 'with hash request' do
subject(:update) { project.update(project_id, request) }
let(:request) do
{
name: 'Updated Project'
}
end
it 'maps response data to Project object' do
expect(update).to be_a(Mailtrap::Project)
expect(update).to have_attributes(
name: 'Updated Project'
)
end
end
🤖 Prompt for AI Agents
In spec/mailtrap/projects_api_spec.rb around lines 129 to 142, the "with hash
request" context never overrides the subject to send a positional Hash (it still
uses keyword args), so update the context to override the subject to call the
update method with the project id and the request Hash as a positional argument
(i.e. subject(:update) { described_class.update(project_id, request) }) so the
spec actually tests the hash path; keep the rest of the expectations unchanged.

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

Caution

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

⚠️ Outside diff range comments (1)
spec/spec_helper.rb (1)

54-58: Ensure URI matching ignores the account id segment for playback stability.

Since cassettes record ACCOUNT_ID in the URI while live requests use a real id, replace :uri with a custom matcher that normalizes the account segment.

Apply this diff:

-  config.default_cassette_options = {
-    match_requests_on: [:method, :uri, :body, header_matcher],
+  # Use a URI matcher that ignores the numeric account id segment.
+  config.default_cassette_options = {
+    match_requests_on: [:method, :uri_ignoring_account_id, :body, header_matcher],
     allow_unused_http_interactions: false,
     record: :once
   }

And register the matcher (add below the before_record block):

config.register_request_matcher :uri_ignoring_account_id do |r1, r2|
  norm = ->(u) { u.gsub(%r{/accounts/\d+/}, '/accounts/ACCOUNT_ID/') }
  norm.call(r1.uri) == norm.call(r2.uri)
end
♻️ Duplicate comments (1)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (1)

163-164: Share links correctly sanitized in both interactions.

Admin/viewer share URLs are redacted consistently. This resolves the earlier leakage concern.

Also applies to: 326-327

🧹 Nitpick comments (54)
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (3)

61-148: Trim CSP header noise from cassettes to improve fixture stability.

The Content-Security-Policy blocks are very long and change frequently (domains, nonces). They don’t influence how the client handles 201/404 logic here and will cause unnecessary fixture diffs. Consider stripping CSP during recording.

Add a before_record hook (in spec_helper or VCR config) to drop CSP and other volatile headers:

# spec/spec_helper.rb (or wherever VCR is configured)
VCR.configure do |c|
  c.before_record do |i|
    %w[
      Content-Security-Policy
      Report-To
      Nel
    ].each { |h| i.response.headers.delete(h) }
  end
end

Also applies to: 225-313


27-27: Strip highly volatile headers and metadata (date, etag, tracing, cf-ray, ratelimit) to reduce churn.

These fields are expected to vary across recordings and don’t affect client behavior in these tests. Keeping them inflates diffs and makes reviews noisy.

Recommended VCR sanitization:

VCR.configure do |c|
  c.before_record do |i|
    %w[
      Date
      Etag
      X-Request-Id
      X-Runtime
      X-Cloud-Trace-Context
      Cf-Ray
      Server
      X-Ratelimit-Remaining
      Alt-Svc
    ].each { |h| i.response.headers.delete(h) }

    # Also remove VCR metadata timestamps if present
    i.recorded_at = nil if i.respond_to?(:recorded_at=)
  end
end

Also applies to: 55-55, 57-57, 150-154, 159-161, 168-169, 193-193, 196-197, 200-201, 221-221, 314-318, 323-325, 330-330


165-167: Optional: redact dynamic resource identifiers in bodies to minimize re-record diffs.

id and uuid are not sensitive, but they’ll change on re-record. If tests don’t consume these values, consider normalizing them.

Example normalization:

VCR.configure do |c|
  c.before_record do |i|
    body = i.response.body
    next unless body && body.is_a?(String)
    body = body.gsub(/"id":\s*\d+/, '"id":TEMPLATE_ID')
               .gsub(/"uuid":\s*"[0-9a-f-]+"/i, '"uuid":"TEMPLATE_UUID"')
    i.response.body = body
  end
end
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (3)

27-36: Reduce cassette churn: filter volatile infra headers (Date, Server, Cf-Ray, X-Request-Id, X-Runtime, X-Cloud-Trace-Context).

These values change on every recording, causing noisy diffs without adding test value. Consider dropping them at record-time.

Add a before_record hook in spec_helper.rb:

VCR.configure do |c|
  volatile_headers = %w[
    Date Server X-Request-Id X-Runtime X-Cloud-Trace-Context Cf-Cache-Status Cf-Ray Alt-Svc
    Connection Transfer-Encoding Etag
  ]
  c.before_record do |i|
    volatile_headers.each { |h| i.response.headers.delete(h) }
  end
end

Also applies to: 149-161, 192-201, 312-324


60-148: Normalize or drop Content-Security-Policy header with nonces.

CSP strings and nonces are highly dynamic and bloat fixtures. Unless tests assert CSP, remove or normalize them.

Apply this spec_helper.rb tweak:

VCR.configure do |c|
  c.before_record do |i|
    if i.response.headers['Content-Security-Policy']
      # Option A: remove entirely
      i.response.headers.delete('Content-Security-Policy')
      # Option B: normalize nonces instead (pick one approach)
      # i.response.headers['Content-Security-Policy'] =
      #   i.response.headers['Content-Security-Policy'].map { |v| v.gsub(/'nonce-[^']+'/, "'nonce-<REDACTED>'") }
    end
  end
end

Also applies to: 224-311


165-168: Scrub dynamic timestamps in response body to stabilize recordings.

created_at/updated_at change every record and aren’t needed for 404 deletion behavior.

You can normalize them during recording:

VCR.configure do |c|
  c.before_record do |i|
    if i.response.headers['Content-Type']&.any? { |v| v.include?('application/json') }
      begin
        body = JSON.parse(i.response.body)
        %w[created_at updated_at].each do |k|
          body[k] = '<TIMESTAMP>' if body.key?(k)
        end
        i.response.body = body.to_json
      rescue JSON::ParserError
        # ignore non-JSON bodies
      end
    end
  end
end
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (5)

26-26: Normalize or drop volatile timestamps (Date, recorded_at) to reduce cassette churn.

These values change every re-record and add noise to diffs. Strip them at record time.

Add to VCR config:

VCR.configure do |c|
  c.before_record do |i|
    i.response.headers.delete("Date")
    i.recorded_at = Time.utc(1970,1,1) # or leave nil
  end
end

Also applies to: 165-165


33-34: Remove Server header from cassette (environment-specific, noisy).

Server: cloudflare is infra-specific and not relevant to assertions.

Add to the same before_record hook:

i.response.headers.delete("Server")

60-147: Huge CSP header is highly volatile; recommend stripping to keep fixtures stable and small.

CSP includes nonces, domains, and vendor updates that churn often. Keeping it inflates diffs without adding test value.

In spec_helper.rb:

VCR.configure do |c|
  c.before_record do |i|
    i.response.headers.delete("Content-Security-Policy")
  end
end

Alternatively, redact its value to a placeholder:

i.response.headers["Content-Security-Policy"] = ["<CSP-REDACTED>"]

149-154: Redact or drop ephemeral tracing headers (X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-*).

These are random per request and cause unnecessary cassette updates.

Extend the filter:

%w[
  X-Request-Id
  X-Runtime
  X-Cloud-Trace-Context
  Cf-Cache-Status
  Cf-Ray
  Alt-Svc
].each { |h| i.response.headers.delete(h) }

Also applies to: 158-161


55-56: Consider removing X-Ratelimit-Remaining from fixtures.

This value depends on recording-time quota. It’s not actionable for unit tests.

Add to the header drop list:

i.response.headers.delete("X-Ratelimit-Remaining")
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (5)

26-36: Strip highly volatile infra headers from cassettes to reduce churn.

Headers like Date, Server, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-Cache-Status, Cf-Ray, Alt-Svc change on every re-record and create noisy diffs. Prefer removing them at record time.

Add a before_record filter in spec/spec_helper.rb:

VCR.configure do |c|
  c.before_record do |i|
    %w[
      Date Server Etag Content-Length Transfer-Encoding Alt-Svc
      X-Request-Id X-Runtime X-Cloud-Trace-Context
      Cf-Cache-Status Cf-Ray Strict-Transport-Security Vary
    ].each { |h| i.response.headers.delete(h) }
  end
end

Also applies to: 150-161


55-55: Avoid persisting rate-limit counters in fixtures.

X-Ratelimit-Remaining is environment-dependent and not relevant for assertions; remove it alongside other volatile headers.

Extend the suggested before_record header list with: X-Ratelimit-Remaining, X-Ratelimit-Limit.


60-148: Consider dropping the entire Content-Security-Policy header.

CSP is long and frequently changes (nonce, host allow-list), causing unnecessary cassette diffs. Unless specs assert on CSP, strip it.

In the same before_record hook:

i.response.headers.delete('Content-Security-Policy')

If you need to keep presence, replace with a placeholder:

i.response.headers['Content-Security-Policy'] = ['REDACTED_CSP']

165-168: Timestamps in response body are inherently volatile.

If tests don’t assert exact created_at/updated_at, consider normalizing them during recording to reduce noise.

Example (spec/spec_helper.rb):

VCR.configure do |c|
  c.before_record do |i|
    next unless i.response.headers['Content-Type']&.any? { |v| v.include?('application/json') }
    begin
      body = JSON.parse(i.response.body)
      %w[created_at updated_at].each { |k| body[k] = '2000-01-01T00:00:00Z' if body.key?(k) }
      i.response.body = body.to_json
      i.response.headers.delete('Content-Length')
    rescue JSON::ParserError
      # skip non-JSON bodies
    end
  end
end

17-18: Token is redacted — good. Consider unifying placeholder naming.

You use <BEARER_TOKEN>; ensure spec_helper also filters any Authorization variants to this placeholder.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (4)

26-36: Remove ephemeral infra/tracing headers.

Date/Server/X-Request-Id/X-Runtime/X-Cloud-Trace-Context/Cf-* vary per run; not useful for assertions.

Use the same before_record header deletion list suggested in the first file.

Also applies to: 150-161, 191-201, 315-327


60-149: CSP header churn — drop or redact.

These long CSP blocks will reflow with nonces and source lists; strip or replace with a placeholder unless explicitly asserted.

Also applies to: 225-314


55-55: Rate-limit counters should not be committed.

Remove X-Ratelimit-* headers during recording.

Also applies to: 220-221


165-168: Stabilize body timestamps if not asserted.

Normalize created_at/updated_at to a fixed value and drop Content-Length to prevent mismatches post-transform.

Also applies to: 329-333

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (4)

26-36: Trim volatile transport/edge headers.

Same guidance: drop Date/Server/X-/Cf- headers at record time.

Also applies to: 150-161, 191-201, 315-327


60-149: Reduce CSP noise.

Strip or replace CSP header; it’s unrelated to Mailtrap client behavior.

Also applies to: 225-314


55-55: Avoid persisting rate-limit and etag.

Etag changes frequently as well; unless you assert it, remove.

Extend header deletion list with Etag if not already present.

Also applies to: 219-223


165-168: Normalize timestamps in JSON body if tests don’t assert them.

Prevents re-record churn.

Also applies to: 329-333

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (4)

26-36: Delete volatile headers on record.

Same rationale: Date/Server/X-/Cf- headers are noise in version control.

Also applies to: 150-161


60-148: CSP header: strip or stub.

Recommend redacting to 'REDACTED_CSP' or removing entirely.


55-55: Drop X-Ratelimit- headers.*

Not actionable in tests; fluctuates per environment.


165-168: Stabilize JSON timestamps if not required for assertions.

Consider the normalization hook shown earlier; also remove Content-Length after mutation.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (4)

26-36: Remove volatile headers (Date/Server/X-/Cf-).

Prevents frequent cassette diffs unrelated to client behavior.

Also applies to: 150-161, 192-201, 315-327


60-149: CSP header is excessively noisy here as well.

Recommend stripping or replacing with a placeholder.

Also applies to: 227-315


55-55: Drop X-Ratelimit- and Etag headers.*

They vary between runs and provide no test value.

Also applies to: 221-224


165-168: Body timestamps normalization.

Same suggestion: normalize created_at/updated_at if tests don’t need exact values; remove Content-Length after mutation.

Also applies to: 330-334

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (4)

26-36: Strip highly volatile response headers in VCR to reduce cassette churn.

Headers like Date, Server, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-Ray are inherently unstable and will force frequent cassette updates. Prefer removing them at record-time.

Add this to spec/spec_helper.rb (or your VCR setup) to drop volatile headers:

VCR.configure do |c|
  c.before_record do |i|
    %w[
      Date Server X-Request-Id X-Runtime X-Cloud-Trace-Context
      Cf-Cache-Status Cf-Ray Alt-Svc
    ].each { |h| i.response.headers.delete(h) }
  end
end

I can extend this to other files in the suite on request.

Also applies to: 150-161


55-58: Normalize dynamic counters/tags (rate limit and ETag).

X-Ratelimit-Remaining and Etag will also drift between recordings. Consider redacting or normalizing them to a fixed placeholder to stabilize diffs.

Suggested tweak to the same VCR hook:

VCR.configure do |c|
  c.before_record do |i|
    %w[X-Ratelimit-Remaining Etag].each do |h|
      i.response.headers[h] = ['<REDACTED>'] if i.response.headers[h]
    end
  end
end

Also applies to: 221-224


60-149: CSP header contains long, nonce-bearing policies that cause noisy fixtures.

Content-Security-Policy includes per-response nonces and a very long directive list. Unless your tests assert CSP, redact or drop the header for leaner, more stable cassettes.

Add to the VCR hook:

VCR.configure do |c|
  c.before_record do |i|
    if i.response.headers['Content-Security-Policy']
      i.response.headers['Content-Security-Policy'] = ['<REDACTED_CSP>']
    end
  end
end

Also applies to: 227-315


61-149: Consider redacting potential internal endpoints in CSP.

The CSP includes report-uri to errors.rw.rw with a sentry_key. While it’s from a response header, redacting minimizes accidental disclosure of internal telemetry endpoints.

Integrate in the CSP redaction above or specifically:

i.response.headers['Content-Security-Policy'] =
  [i.response.headers['Content-Security-Policy'].first.gsub(/report-uri\s+\S+/, 'report-uri <REDACTED>')]
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (2)

192-201: Drop volatile headers (Date/Server/etc.) from the cassette.

Same rationale as other fixtures—keeps diffs minimal and reduces re-record churn.

See shared VCR before_record hook suggestion in my earlier comment to strip these headers.


218-223: Normalize X-Ratelimit-Remaining and Etag.

Recommend redacting/normalizing these to avoid needless fixture updates.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (2)

26-36: Stabilize volatile headers in VCR (Date/Server/etc.).

Recommend removing or redacting these at record time.

Also applies to: 191-201


61-149: Redact or drop CSP header with nonces.

Long, nonce-based CSP values cause constant churn; prefer redaction unless explicitly asserted.

Also applies to: 226-314

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (2)

227-275: CSP header should be redacted to minimize fixture noise.

Same recommendation as other files—replace with placeholder or remove.

Also applies to: 109-149


219-224: Remove or normalize volatile headers (rate limits, ids, trace headers).

This will make cassette diffs far more stable over time.

Also applies to: 150-161, 192-201

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (3)

26-36: Prune volatile headers at record time (Date, Server, trace ids).

Same guidance as other cassettes.

Also applies to: 192-202


61-149: Redact CSP header with nonce.

Replace with a placeholder via VCR hook unless you assert on CSP explicitly.

Also applies to: 227-315


55-58: Normalize X-Ratelimit-Remaining and Etag to placeholders.

Prevents unnecessary cassette changes.

Also applies to: 219-224

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (4)

26-36: Strip volatile headers to reduce cassette churn.

Date, Server, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-* and Etag change on every record and bloat diffs. Recommend removing them at record time.

Add in spec_helper:

VCR.configure do |c|
  c.before_record do |i|
    %w[
      Date Server X-Request-Id X-Runtime X-Cloud-Trace-Context
      Cf-Cache-Status Cf-Ray Alt-Svc Etag
      X-Ratelimit-Remaining
    ].each { |h| i.response.headers.delete(h) }
  end
end

Also applies to: 55-58, 149-163, 168-169


60-148: Consider excluding Content-Security-Policy from cassettes.

CSP is extremely long and frequently changes (nonces, hosts). It creates large, noisy diffs without test value. Suggest dropping this header during recording.

In spec_helper:

VCR.configure do |c|
  c.before_record { |i| i.response.headers.delete('Content-Security-Policy') }
end

30-36: Normalize body length headers.

Content-Length vs Transfer-Encoding: chunked varies by edge server and causes mismatches across recordings. Consider deleting both headers to stabilize fixtures.

In spec_helper:

VCR.configure do |c|
  c.before_record do |i|
    i.response.headers.delete('Content-Length')
    i.response.headers.delete('Transfer-Encoding')
    i.response.headers.delete('Connection')
  end
end

Also applies to: 195-201


220-221: Optional: avoid asserting rate-limit counters and recorded_at.

X-Ratelimit-Remaining and recorded_at are incidental. Removing them trims diffs and fragility.

In spec_helper, also delete 'X-Ratelimit-Remaining'. recorded_at is added by VCR; keeping it is harmless, but if it creates merge churn you can post-process cassettes in CI to strip it.

Also applies to: 329-329

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (2)

26-36: Reduce fixture volatility by dropping non-deterministic headers.

These headers change per run and add noise: Date, Server, Etag, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-*, Alt-Svc, X-Ratelimit-Remaining.

Leverage the same before_record hook proposed earlier to delete them globally.

Also applies to: 55-58, 149-163, 168-168, 191-201, 213-215, 306-318, 323-323


60-148: Exclude CSP from recordings.

The extensive CSP block changes often and offers no assertion value for the EmailTemplates behavior. Remove it during recording to keep cassettes lean.

Also applies to: 217-305

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (3)

26-36: Trim volatile headers (Date, Server, Etag, IDs, trace).

Recommend the same before_record header deletion here to avoid frequent cassette updates.

Also applies to: 55-58, 149-161, 168-168, 191-201, 220-223, 315-326, 333-334


60-148: Drop CSP headers from POST/PATCH responses.

Large, frequently changing CSP policies add no test value for model mapping or update behavior assertions. Remove at record time.

Also applies to: 225-314


27-27: Stabilize body-length-related headers.

Delete Content-Length/Transfer-Encoding/Connection during recording for stability across CDN/edge variations.

Also applies to: 192-193

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a2b6be6 and 82833a9.

📒 Files selected for processing (42)
  • lib/mailtrap/project.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/returns_an_array_of_EmailTemplate_objects.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (3 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/mailtrap/projects_api_spec.rb (1 hunks)
  • spec/spec_helper.rb (1 hunks)
💤 Files with no reviewable changes (9)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/returns_an_array_of_EmailTemplate_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/returns_an_EmailTemplate_object.yml
✅ Files skipped from review due to trivial changes (3)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml
🚧 Files skipped from review as they are similar to previous changes (9)
  • lib/mailtrap/project.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/mailtrap/projects_api_spec.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml
🧰 Additional context used
🧬 Code Graph Analysis (1)
spec/spec_helper.rb (1)
lib/mailtrap/mail/base.rb (1)
  • to_json (64-69)
🔇 Additional comments (30)
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (5)

5-5: Good: endpoint path parametrized to ACCOUNT_ID.

POST now targets accounts/ACCOUNT_ID/email_templates, aligning with the new redaction strategy and preventing hardcoded IDs from leaking into fixtures.


171-171: Good: PATCH path also parametrized to ACCOUNT_ID.

Consistent use of ACCOUNT_ID across EmailTemplates endpoints reduces future cassette churn when running against different accounts.


17-20: Good: Authorization header is redacted.

Bearer token is masked with <BEARER_TOKEN>, aligning with the suite’s redaction policy.


5-5: All fixtures sanitized: no raw account IDs or unmasked tokens

  • Ran rg -nP '/api/accounts/\d+/' across spec/fixtures/vcr_cassettes and found no raw numeric account IDs.
  • Ran rg -nP 'Bearer (?!<)' and confirmed there are no unmasked Bearer tokens.
  • Ran rg -nP '/api/accounts/ACCOUNT_ID/' and verified every cassette consistently uses the ACCOUNT_ID placeholder.

All checks pass; no further action needed.


30-31: No header assertions found in specs – suggestion not required
I ran a repository-wide search for any spec asserting on Content-Length, Transfer-Encoding, Server, Cf-Ray, or Etag headers and found no matches. Since there are currently no tests verifying header presence or values, there’s no brittleness risk here.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (4)

195-201: No custom middleware inspects Content-Length or Transfer-Encoding
Ran rg -nP '\b(Content-Length|Transfer-Encoding)\b' -C3 lib and found no occurrences. No further action needed.


17-20: No token leaks detected in VCR fixtures

I’ve searched all spec/fixtures/vcr_cassettes/** for real credential patterns (Bearer tokens, API keys, token fields) and verified that every Authorization header uses the <BEARER_TOKEN> placeholder and no actual secrets appear in either request or response bodies. These fixtures are safe to merge.


5-5: All VCR cassettes consistently use the ACCOUNT_ID placeholder and it’s scrubbed in spec_helper.rb

  • Search for raw numeric IDs in spec/fixtures/vcr_cassettes/** returned no matches for accounts/\d+, confirming all URIs use the ACCOUNT_ID placeholder.
  • spec/spec_helper.rb includes
    config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
    ensuring real account IDs are filtered out before recordings.

No further changes required.


3-169: I don’t see the spec actually referencing that initial POST—your grep used underscores but the example text in the spec is almost certainly written with spaces. Can you please re-verify using the actual descriptions? For example:

# Locate the “when template does not exist” example
rg -n "when template does not exist" -C3 spec/mailtrap/email_templates_api_spec.rb

# Locate the “raises not found error” assertion
rg -n "raises not found error" -C2 spec/mailtrap/email_templates_api_spec.rb

Running those should show that the example only calls delete (and asserts the 404), with no create/POST in the example itself—confirming the POST interaction can be safely removed from the cassette.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1)

16-19: Auth header redaction looks good.

Authorization: Bearer <BEARER_TOKEN> is properly sanitized. No action needed.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1)

5-10: Endpoint placeholder and payload look good.

Switching to ACCOUNT_ID in the URI and expanding the invalid payload fields are consistent with the new redaction and spec expectations.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml (1)

163-165: Create cassette maps cleanly to Project and share_links are sanitized.

Response shape matches the DTO fields and tokens are redacted.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1)

163-165: Update flow cassette is consistent and sanitized.

Both create and update responses map cleanly to Project and redact share tokens.

Also applies to: 326-327

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (1)

5-5: ACCOUNT_ID placeholder in URI looks good.

Consistent parameterization reduces future cassette churn and leaking of real account IDs.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (1)

5-5: Good: Endpoint generalized to ACCOUNT_ID.

Keeps fixtures reusable across environments.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (1)

5-5: LGTM on URI parameterization.

Both create and patch use ACCOUNT_ID consistently.

Also applies to: 171-171

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (1)

5-5: Good move to placeholder account id.

Consistent with other updated fixtures.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (2)

5-5: URI placeholdering is consistent across create and patch.

Nice consistency.

Also applies to: 171-171


174-176: Tests correctly target only the specified fields

I reviewed the VCR fixture for when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml and its associated spec. The request payload includes only the category and body_html fields, and the recorded response shows:

  • category and body_html updated
  • updated_at advanced by the server to reflect the change
  • all other attributes (id, uuid, name, subject, body_text, created_at) preserved

There are no assertions in this test on any headers or unrelated metadata. Everything else remains unchanged, so the spec’s expectations align exactly with “updates only the specified fields.”

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (1)

5-5: Good move: parameterized account id in the URI.

Using ACCOUNT_ID improves fixture portability and avoids accidental leakage of real account ids.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (2)

5-5: Nice: ACCOUNT_ID placeholder applied consistently.

Both POST and PATCH URIs are generalized. This keeps fixtures reusable across environments.

Also applies to: 171-171


329-333: Behavior check passes: only body_html updated, others preserved.

Response reflects body_html change and leaves name/subject/category untouched as intended for this test.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (2)

5-5: Good: URIs now use ACCOUNT_ID for both create and get.

Reduces leakage and aligns with updated spec_helper redaction.

Also applies to: 171-171


164-168: The script will search your spec files for any brittle expectations on id, uuid, created_at, or updated_at. Once we have the output, we can confirm whether such exact-value assertions exist and advise appropriately.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (2)

5-5: ACCOUNT_ID placeholder usage is consistent.

Nice consistency on both POST and PATCH endpoints.

Also applies to: 171-171


330-334: Behavior looks correct: specific fields updated, others intact.

The response shows category and body_html updated, while name/subject/body_text remain unchanged. Matches the spec intent.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (2)

5-5: URI generalization to ACCOUNT_ID is correct.

Keeps sensitive account numbers out of the repo.

Also applies to: 171-171


330-334: Verified absence of strict ETag/rate-limit/timestamp assertions

Ran a repository-wide search for any specs matching X-Ratelimit-Remaining, Etag, created_at, or updated_at against literal expectations (e.g. to, eq, match) and found no occurrences. All mapping specs correctly focus on mutable content fields only.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (1)

165-168: Nice: IDs consistent across create/delete within the same cassette.

The DELETE targets the id returned by the preceding POST (45475), keeping the flow realistic and deterministic. Looks good.

Also applies to: 321-323

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (1)

329-333: Good: PATCH response preserves unrelated fields.

Only category changed, while name, subject, and bodies remain intact. This aligns with the spec intent for partial updates.

- request:
method: post
uri: https://mailtrap.io/api/accounts/1111111/email_templates
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify cassette matching with ACCOUNT_ID placeholder.

You've replaced the literal account id with ACCOUNT_ID in the URI. VCR matches on the literal recorded URI during playback, so unless you added a custom request matcher (or other normalization) the runtime request to /accounts//... will not match /accounts/ACCOUNT_ID/.... Please confirm that spec/spec_helper.rb registers a matcher that strips or normalizes the account segment, not just redacts on recording.

Run this to confirm the matcher exists:


🏁 Script executed:

#!/bin/bash
# Expect to see a custom matcher or match_requests_on excluding the account id path segment.
rg -nC2 -e 'register_request_matcher|match_requests_on' -e 'ACCOUNT_ID' -e 'filter_sensitive_data' spec/spec_helper.rb spec/**/*

Length of output: 52556


Add a custom VCR request matcher to normalize the ACCOUNT_ID segment

I verified spec/spec_helper.rb and confirmed there is only a filter_sensitive_data('ACCOUNT_ID') call (which redacts the real account ID in recordings) and the default cassette options:

config.default_cassette_options = {
  match_requests_on: [:method, :uri, :body, header_matcher],
  allow_unused_http_interactions: false,
  record: :once
}

Because VCR matches on the literal recorded URI, any runtime request to /accounts/<numeric>/… will not match the placeholder /accounts/ACCOUNT_ID/… in the cassette. To fix this, register a matcher that strips or normalizes the account-ID segment before comparison. For example, in spec/spec_helper.rb:

VCR.configure do |config|
  # Register a matcher that normalizes the account ID to the placeholder
  config.register_request_matcher :uri_without_account_id do |req1, req2|
    normalize = ->(uri) { uri.gsub(%r{/accounts/\d+(/|$)}, '/accounts/ACCOUNT_ID\1') }
    normalize.call(req1.uri) == normalize.call(req2.uri)
  end

  # Use the new matcher instead of the plain :uri matcher
  config.default_cassette_options = {
    match_requests_on: [:method, :uri_without_account_id, :body, header_matcher],
    allow_unused_http_interactions: false,
    record: :once
  }
end

• Please update spec/spec_helper.rb to register and use this custom matcher so that recorded cassettes with the ACCOUNT_ID placeholder will correctly match runtime requests containing the actual numeric account ID.

🤖 Prompt for AI Agents
In
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml
around line 5, the cassette contains a placeholder ACCOUNT_ID which prevents
runtime URIs with numeric account IDs from matching; update spec/spec_helper.rb
to register a custom VCR request matcher (e.g., :uri_without_account_id) that
normalizes URIs by replacing /accounts/<digits>/ with /accounts/ACCOUNT_ID/
before comparison, and change config.default_cassette_options to use
:uri_without_account_id instead of :uri so recorded cassettes with the
ACCOUNT_ID placeholder match runtime requests.

- request:
method: get
uri: https://mailtrap.io/api/accounts/1111111/email_templates
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Good: endpoint generalized to placeholder ACCOUNT_ID. Verify VCR matching won’t break.

Changing the recorded URI to use ACCOUNT_ID improves redaction, but by default VCR matches on the full URI. Unless you also normalize the request URI during matching (custom matcher) this cassette may not match playback when the runtime uses a numeric account id.

Run the script below to confirm a custom matcher/redaction is in place and to ensure no specs assert on brittle headers:

If a matcher is missing, consider this spec_helper snippet:

# In spec/spec_helper.rb
require "uri"

VCR.configure do |c|
  # Redact the account id in recorded artifacts
  c.filter_sensitive_data("ACCOUNT_ID") { ENV.fetch("MAILTRAP_ACCOUNT_ID", "1111111") }

  # Normalize account id segment during matching to avoid playback mismatches
  c.register_request_matcher(:method_host_path_wo_account) do |r1, r2|
    norm = ->(u) {
      uri = URI(u)
      uri.path = uri.path.gsub(%r{(/accounts/)\d+}, '\1ACCOUNT_ID')
      [uri.scheme, uri.host, uri.path, uri.query]
    }
    [r1.method, *norm.call(r1.uri)] == [r2.method, *norm.call(r2.uri)]
  end
  c.default_cassette_options = { match_requests_on: [:method_host_path_wo_account] }
end

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate VCR config and redactions =="
fd -a -HI 'spec_helper*.rb' || true
rg -nC3 "VCR\.configure" spec || true
rg -nC3 "filter_sensitive_data\('ACCOUNT_ID'\)|register_request_matcher|match_requests_on|before_record|before_playback" spec || true

echo "== Ensure no tests assert on dynamic headers =="
rg -n "Content-Security-Policy|Cf-Ray|X-Request-Id|X-Cloud-Trace-Context|X-Runtime|Date|Server:" spec -g '!**/fixtures/**' || true

echo "== Check cassettes consistently use ACCOUNT_ID placeholder =="
rg -nP "accounts/\d+/email_templates" spec/fixtures/vcr_cassettes || true

Length of output: 1879


Action Required: Add a custom VCR matcher to normalize the ACCOUNT_ID segment

We’ve confirmed that:

  • You already redact the account ID at record time via
    config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') } (spec/spec_helper.rb:24).
  • However, your cassette matching still uses the raw URI (:uri) (spec/spec_helper.rb:55), and there is no register_request_matcher or before_playback normalizer for the /accounts/:id segment.
  • No tests assert on dynamic headers (no matches for Content-Security-Policy|Cf-Ray|…|Server: outside fixtures).
  • All existing cassettes have been updated to use ACCOUNT_ID—we found no lingering numeric IDs under spec/fixtures/vcr_cassettes.

Without a custom matcher, playback will fail whenever ENV['MAILTRAP_ACCOUNT_ID'] differs from the literal placeholder in the YAML. To fix this, introduce a matcher that strips or normalizes the numeric ID:

# spec/spec_helper.rb

 VCR.configure do |config|
   config.cassette_library_dir     = 'spec/fixtures/vcr_cassettes'
   config.hook_into                :webmock
   config.configure_rspec_metadata!
   config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }

+  config.register_request_matcher :method_host_path_wo_account do |r1, r2|
+    normalize = ->(uri_str) {
+      uri = URI(uri_str)
+      uri.path = uri.path.gsub(%r{(/accounts/)\d+}, '\1ACCOUNT_ID')
+      [uri.scheme, uri.host, uri.path, uri.query]
+    }
+    [r1.method, *normalize.call(r1.uri)] == [r2.method, *normalize.call(r2.uri)]
+  end
+
-  config.default_cassette_options = {
-    match_requests_on: [:method, :uri, :body, header_matcher],
+  config.default_cassette_options = {
+    match_requests_on: [:method_host_path_wo_account, :body, header_matcher],
     allow_unused_http_interactions: false,
     record:                     :once
   }
 end

• Ensure this matcher is applied globally via default_cassette_options.
• Verify that any future cassettes don’t include raw IDs and that playback succeeds when MAILTRAP_ACCOUNT_ID changes.

📝 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
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates
# spec/spec_helper.rb
VCR.configure do |config|
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
config.hook_into :webmock
config.configure_rspec_metadata!
config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
# Normalize /accounts/:id to use the ACCOUNT_ID placeholder for matching
config.register_request_matcher :method_host_path_wo_account do |r1, r2|
normalize = ->(uri_str) {
uri = URI(uri_str)
uri.path = uri.path.gsub(%r{(/accounts/)\d+}, '\1ACCOUNT_ID')
[uri.scheme, uri.host, uri.path, uri.query]
}
[r1.method, *normalize.call(r1.uri)] == [r2.method, *normalize.call(r2.uri)]
end
config.default_cassette_options = {
match_requests_on: [:method_host_path_wo_account, :body, header_matcher],
allow_unused_http_interactions: false,
record: :once
}
end
🤖 Prompt for AI Agents
In
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml
around line 5: playback will fail because VCR is matching the raw :uri
containing a numeric account ID while cassettes use the placeholder ACCOUNT_ID;
add a custom VCR request matcher (e.g., :normalized_uri) in spec/spec_helper.rb
that normalizes the request URI by replacing the /accounts/:id segment with the
ACCOUNT_ID placeholder (or removing the numeric ID) before comparison, register
that matcher with VCR.register_request_matcher, and include it in VCR.configure
default_cassette_options :match_requests_on so all cassettes use the normalized
matcher during playback.

Comment on lines +165 to +168
string: '{"id":45466,"uuid":"84c7a74c-8599-40f8-9d8d-d32ee7c6a482","name":"Original
Template","category":"Original Category","subject":"Original Subject","body_html":"<div>Original
HTML</div>","body_text":"Original Text","created_at":"2025-06-23T06:54:52.422Z","updated_at":"2025-06-23T06:54:52.422Z"}'
recorded_at: Mon, 23 Jun 2025 06:54:52 GMT
HTML</div>","body_text":"Original Text","created_at":"2025-08-20T19:39:38.791Z","updated_at":"2025-08-20T19:39:38.791Z"}'
recorded_at: Wed, 20 Aug 2025 19:39:38 GMT
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid asserting on ids/timestamps in tests.

The resource id and ISO8601 timestamps are environment-dependent. Ensure specs map/deserialize fields but don’t hard-assert specific ids or dates.

Run to spot brittle expectations:

Also applies to: 330-334


🏁 Script executed:

#!/bin/bash
# Look for exact numeric id or timestamp assertions in specs
rg -nP --type=ruby -C2 '\.to\s+eq\(\s*\d{5,}\s*\)|"20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}' spec

Length of output: 96


I suspect the raw YAML fixtures still contain hard-coded IDs and timestamps that the previous Ruby-only grep missed. Let’s scan the fixture directory for any literal "id": <number> or ISO8601 timestamps:


🏁 Script executed:

#!/bin/bash
# Search all YAML fixtures for literal numeric IDs and ISO8601 timestamps
rg -nP '"id":\s*\d+' -n spec/fixtures
rg -nP '"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}' -n spec/fixtures

Length of output: 173605


Avoid Hard-Coding IDs and Timestamps in VCR Fixtures

The YAML fixtures under spec/fixtures/vcr_cassettes embed literal resource IDs and ISO8601 timestamps, which makes tests brittle and environment-dependent. Instead of asserting on exact values, filter or ignore these dynamic fields.

Please refactor as follows:

• In your VCR configuration (e.g. spec/support/vcr.rb), add filters for IDs and timestamps, for example:

VCR.configure do |c|
  c.filter_sensitive_data('<EMAIL_TEMPLATE_ID>') { |interaction|
    interaction.response.body[/\"id\":\s*(\d+)/, 1]
  }
  c.filter_sensitive_data('<CREATED_AT>') { |interaction|
    interaction.response.body[/\"created_at\":\"(.*?)\"/, 1]
  }
  c.filter_sensitive_data('<UPDATED_AT>') { |interaction|
    interaction.response.body[/\"updated_at\":\"(.*?)\"/, 1]
  }
end

• Re-record the affected cassettes so that IDs and timestamps are replaced with the placeholders above.
– spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (lines 165–168, 331–333)
– spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/maps_response_data_to_EmailTemplate_objects.yml (all blocks with created_at/updated_at)
– spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml
– And similarly under Mailtrap_ProjectsAPI for any id, created_at or updated_at fields.

• In your model-mapping specs, assert only on the presence and format of these fields (e.g. use be_a(Integer) or match(/\d{4}-\d{2}-\d{2}T/)) rather than exact values.

This will decouple tests from environment-specific data and ensure they remain stable.

- request:
method: post
uri: https://mailtrap.io/api/accounts/1111111/email_templates
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Re-check ACCOUNT_ID placeholder playback behavior.

As above, ensure your matcher normalizes the account id path segment so that runtime URIs with numeric ids match the recorded /accounts/ACCOUNT_ID/... URIs.

Also applies to: 171-171


🏁 Script executed:

#!/bin/bash
# Also check if any custom URI normalizer is used for VCR/WebMock.
rg -nC2 -e 'uri_parser|normalize|Addressable|webmock' spec/spec_helper.rb spec/**/*

Length of output: 1003


Add VCR request matcher to normalize the ACCOUNT_ID path segment

It looks like we don’t currently normalize the /accounts/… segment, so any recorded cassette using the literal ACCOUNT_ID won’t match a runtime URI with a numeric ID. The rg search in spec/spec_helper.rb confirms there’s no custom URI normalizer or request matcher in the VCR configuration.

Please update your VCR setup to register a custom matcher that rewrites the account‐ID segment to the placeholder before comparing, for example:

# spec/spec_helper.rb

 VCR.configure do |config|
   config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
   config.hook_into :webmock
+  # Normalize /accounts/<numeric_id>/… to /accounts/ACCOUNT_ID/… for cassette matching
+  config.register_request_matcher :normalized_account_uri do |r1, r2|
+    normalize = ->(uri) {
+      URI(uri).tap { |u|
+        u.path = u.path.gsub(%r{^/accounts/\d+}, '/accounts/ACCOUNT_ID')
+      }.to_s
+    }
+    normalize.call(r1.uri) == normalize.call(r2.uri)
+  }
+  # Ensure VCR uses our custom matcher alongside method and full URI
+  config.default_cassette_options = {
+    match_requests_on: [:method, :normalized_account_uri]
+  }
   config.configure_rspec_metadata!
 end

– Applies to fixtures under
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (line 5)
• the other cassette at line 171

With this in place, any runtime request to /accounts/12345/... will match the recorded /accounts/ACCOUNT_ID/....

📝 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
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates
# spec/spec_helper.rb
VCR.configure do |config|
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
config.hook_into :webmock
# Normalize /accounts/<numeric_id>/… to /accounts/ACCOUNT_ID/… for cassette matching
config.register_request_matcher :normalized_account_uri do |r1, r2|
normalize = ->(uri) {
URI(uri).tap { |u|
u.path = u.path.gsub(%r{^/accounts/\d+}, '/accounts/ACCOUNT_ID')
}.to_s
}
normalize.call(r1.uri) == normalize.call(r2.uri)
end
# Ensure VCR uses our custom matcher alongside method and full URI
config.default_cassette_options = {
match_requests_on: [:method, :normalized_account_uri]
}
config.configure_rspec_metadata!
end
🤖 Prompt for AI Agents
In
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml
around line 5 (and the other cassette at line 171), VCR cassettes contain the
literal ACCOUNT_ID but runtime URIs use numeric account IDs; update
spec/spec_helper.rb to register a custom VCR request matcher that normalizes the
/accounts/<id>/ path segment by replacing any numeric ID with the placeholder
(ACCOUNT_ID) before comparing URIs, and ensure this matcher is added to
VCR.configure (or included in the cassette matchers) so recorded cassettes match
runtime requests.

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: 3

Caution

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

⚠️ Outside diff range comments (1)
spec/spec_helper.rb (1)

54-58: Normalize URIs to decouple matchers from account IDs.

Matching on :uri with a literal ACCOUNT_ID forces everyone’s env to match the cassette placeholder. Register a matcher that normalizes /accounts// to a stable token so both numeric and placeholder URIs match.

Apply:

   config.default_cassette_options = {
-    match_requests_on: [:method, :uri, :body, header_matcher],
+    match_requests_on: [:method, :body, header_matcher, :uri_normalized],
     allow_unused_http_interactions: false,
     record: :once
   }

Add the matcher right above default_cassette_options:

  config.register_request_matcher :uri_normalized do |r1, r2|
    norm = ->(u) { u.to_s.gsub(%r{/accounts/(?:\d+|ACCOUNT_ID)/}, '/accounts/ACCOUNT_ID/') }
    norm.call(r1.uri) == norm.call(r2.uri)
  end
♻️ Duplicate comments (8)
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (1)

26-36: Same header-churn concern; please apply global scrubbing.

This cassette carries the same volatile headers (Date/Server/Cf-Ray/etc.). Prefer a single before_record scrub in spec_helper to keep all fixtures stable.

Also applies to: 51-58, 150-161, 168-169

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (1)

26-36: Apply the same volatile-header scrubbing here.

Large diffs stem from Date/Server/Cf-Ray/ETag/X-* headers; remove at record time.

Also applies to: 51-58, 149-161, 168-169, 192-202, 219-224, 315-327, 334-335

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (1)

26-36: Repeat: remove volatile headers via spec_helper to keep fixtures stable.

Also applies to: 51-58, 150-161, 168-169

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (1)

26-36: Same: scrub volatile headers (Date/Server/Cf-Ray/etc.) to reduce fixture noise.

Also applies to: 51-58, 150-161, 168-169, 191-197, 210-215, 321-324

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (1)

70-147: Normalize request matching for ACCOUNT_ID.

Same matcher advice applies here to ensure playback stability.

Also applies to: 235-312

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (1)

70-147: Ensure ACCOUNT_ID-matching normalization is in place.

Reiterating the custom matcher requirement for reliable playback.

Also applies to: 235-312

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (1)

70-147: Playback safety: custom URI matcher still required.

Same rationale as previous files.

Also applies to: 236-314

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (1)

70-147: Confirm custom matcher for ACCOUNT_ID path normalization.

Re-asserting the need for normalized_uri matcher for replay to work with placeholders.

Also applies to: 233-311

🧹 Nitpick comments (27)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1)

25-161: Optional: strip highly-volatile response headers to reduce cassette churn.

Headers like Date, X-Request-Id, X-Cloud-Trace-Context, Cf-Ray, and CSP nonces change on every recording. Consider pruning them in a before_record hook so future re-records create minimal diffs.

You can extend spec/spec_helper.rb (within the existing before_record) to drop them:

NOISY_HEADERS = %w[
  Date X-Request-Id X-Runtime X-Cloud-Trace-Context Cf-Cache-Status Cf-Ray Server
  Etag Content-Security-Policy Strict-Transport-Security Alt-Svc
].freeze

interaction.response.headers.reject! { |k, _| NOISY_HEADERS.include?(k) }
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1)

25-161: Optional: prune non-deterministic headers to keep fixtures lean.

Same suggestion as other cassettes: filter Date/CSP/Cf-Ray/etc. during recording to minimize noise.

Also applies to: 188-325

spec/spec_helper.rb (1)

26-44: Optional: drop volatile response headers during recording to reduce fixture diffs.

While you’re in before_record, consider removing noisy headers (Date, Cf-Ray, CSP with nonces, etc.) to keep cassettes stable.

You can add inside the same before_record:

  NOISY_HEADERS = %w[
    Date X-Request-Id X-Runtime X-Cloud-Trace-Context Cf-Cache-Status Cf-Ray Server
    Etag Content-Security-Policy Strict-Transport-Security Alt-Svc
  ].freeze
  interaction.response.headers.reject! { |k, _| NOISY_HEADERS.include?(k) }
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (1)

60-147: Optional: filter CSP/nonces and other dynamic headers.

This cassette contains large CSP blocks with runtime nonces and many dynamic headers that will churn on re-record. Consider pruning them (see spec_helper suggestion).

Also applies to: 225-313

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (3)

25-35: Trim volatile response headers in cassettes to reduce churn.

Date, Server, Cf-Ray, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, ETag, Alt-Svc, and similar headers are highly variable and bloat diffs. Unless tests assert them, scrub at record time.

Add (or extend) this in spec/spec_helper.rb:

 VCR.configure do |c|
   c.before_record do |i|
+    %w[
+      Date Server Cf-Ray X-Request-Id X-Runtime X-Cloud-Trace-Context
+      Etag ETag Alt-Svc Strict-Transport-Security
+    ].each { |h| i.response.headers.delete(h) }
   end
 end

Also applies to: 148-160, 165-165


60-147: Consider removing Content-Security-Policy from recorded headers.

CSP is extremely long and changes frequently (e.g., nonce, host lists). It creates noisy diffs with no value for client behavior under test.

You can drop it during recording:

 VCR.configure do |c|
   c.before_record do |i|
+    i.response.headers.delete('Content-Security-Policy')
   end
 end

51-57: Optional: also scrub rate-limit headers if not asserted.

These fluctuate per run and add noise. Keep only if your specs verify them.

-  X-Ratelimit-Limit:
-  X-Ratelimit-Remaining:
+  # Scrub if not asserted:
+  # i.response.headers.delete('X-Ratelimit-Limit')
+  # i.response.headers.delete('X-Ratelimit-Remaining')
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (1)

330-334: PATCH response verifies partial update shape; consider assertions that ignore timestamps.

Your body shows only category and body_html changed. If tests assert updated_at exactly, they’ll be brittle; prefer asserting it changes (not equals previous) rather than pinning the literal timestamp.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (1)

55-57: ETag changes on every re-record; scrub unless asserted.

+    # In spec_helper:
+    # i.response.headers.delete('Etag')
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (2)

27-36: Stabilize volatile headers to reduce cassette churn.

Headers like Date, Server, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-Ray, Etag, and CSP nonces change on every record, causing noisy diffs. Strip them at record time.

Apply in spec/spec_helper.rb (outside this file):

 VCR.configure do |c|
+  c.before_record do |i|
+    %w[
+      Date Server X-Request-Id X-Runtime X-Cloud-Trace-Context
+      Cf-Ray Cf-Cache-Status Alt-Svc Etag Strict-Transport-Security
+      Vary Transfer-Encoding
+    ].each { |h| i.response.headers.delete(h) }
+
+    # Normalize CSP nonce to avoid diffs
+    if (csp = i.response.headers['Content-Security-Policy']&.first)
+      i.response.headers['Content-Security-Policy'] = [
+        csp.gsub(/nonce-js-[^'"]+/, 'nonce-js-REDACTED')
+      ]
+    end
+  end
 end

Also applies to: 55-58, 149-161, 192-201, 220-224


223-231: Consider trimming CSP from fixtures entirely.

CSP changes frequently and is irrelevant for client lib correctness. Either drop the header or keep a single representative value with nonce redaction.

Also applies to: 233-311

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (1)

27-36: Strip unstable headers; normalize CSP nonces as in the previous comment.

Replicate the spec_helper before_record hook to avoid timestamp/nonce-driven churn.

Also applies to: 55-58, 150-161, 192-201, 220-223

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (1)

27-36: Reduce fixture noise by removing volatile headers and CSP nonces.

Same stabilization recommendation as earlier.

Also applies to: 55-58, 150-161, 192-201, 220-223

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (1)

27-36: Apply header and CSP nonce stripping to cut diff noise.

Replicate spec_helper hook from earlier comment.

Also applies to: 55-58, 150-161, 193-201, 221-224

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (1)

27-36: Stabilize volatile headers and CSP as advised previously.

Strip Date/Server/trace ids/CSP nonces to prevent recurring diffs.

Also applies to: 55-58, 150-161, 192-201, 220-224

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (2)

27-36: Reduce cassette churn by filtering volatile headers

Headers like Date, Server, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, and Cf-Ray change run-to-run and create noisy diffs. Consider stripping them in the VCR before_record hook.

I can propose a spec_helper update if you want it in this PR.

Also applies to: 148-158


59-146: Normalize/strip CSP header to avoid nonce/source list flapping

The CSP header is long, includes nonces, and changes frequently. Recording it adds no value to client-side API tests and makes fixtures brittle. Recommend replacing it with a placeholder (e.g., <CSP_REDACTED>) during recording.

If helpful, I’ll share a small before_record snippet to collapse this header across all cassettes.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (2)

34-36: Filter high-churn headers (Server/Cf-Ray/X-Request-Id/etc.)

These headers are inherently volatile and add noise to diffs. Suggest dropping them in a VCR before_record filter.

Also applies to: 150-161, 316-327


61-149: Collapse CSP headers to a placeholder

Same rationale as other cassettes—nonces and allowlists change often and aren’t relevant for API client tests.

Also applies to: 227-314

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (2)

34-36: Trim volatile infra headers

Dropping Server, Cf-Ray, X-Request-Id, etc., will significantly reduce fixture churn without affecting test intent.

Also applies to: 150-161, 315-326


61-149: CSP header normalization recommended

Consider replacing CSP with a fixed placeholder during recording to eliminate nonce/source drift.

Also applies to: 226-314

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (2)

27-36: Consider filtering volatile headers

Same recommendation as other files—filter Date, Server, Cf-Ray, X-Request-Id, X-Runtime, X-Cloud-Trace-Context to avoid noisy diffs.

Also applies to: 150-161, 316-327


61-149: Normalize CSP headers

CSP nonces and allowlists create recurring cassette diffs; replacing with a placeholder improves stability.

Also applies to: 227-315

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (2)

27-36: Optional: filter infra/trace headers

To keep fixtures lean and reduce re-recording noise, consider stripping Server, Date, Cf-Ray, and similar volatile headers at record time.

Also applies to: 192-201


61-149: Optional: collapse CSP header to a placeholder

Prevents nonces and allowlist changes from forcing fixture updates.

Also applies to: 226-314

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (2)

27-36: Trim volatile headers in cassettes to reduce churn and review noise

Headers like Date, Server, X-Request-Id, X-Runtime, X-Cloud-Trace-Context, Cf-Ray, ETag, and X-Ratelimit-* change on every recording and add unnecessary diff noise. They’re not used for request matching, so consider dropping them at record time.

Add a VCR before_record filter in spec_helper.rb:

VCR.configure do |c|
  noisy = %w[
    Date Server X-Request-Id X-Runtime X-Cloud-Trace-Context Cf-Ray Cf-Cache-Status
    Alt-Svc Etag X-Ratelimit-Limit X-Ratelimit-Remaining Strict-Transport-Security
  ]
  c.before_record do |i|
    noisy.each { |h| i.response.headers.delete(h) }
  end
  # Ensure matching ignores headers:
  c.default_cassette_options = {
    match_requests_on: %i[method uri body]
  }
end

Also applies to: 55-58, 150-163, 192-201, 220-223, 315-327


60-148: Consider excluding CSP from recorded headers

The Content-Security-Policy block is extremely large and highly volatile (nonces, host lists), causing frequent cassette updates without test value. Unless you assert on CSP explicitly, remove it during record to keep fixtures stable.

Extend the VCR filter:

VCR.configure do |c|
  c.before_record do |i|
    i.response.headers.delete('Content-Security-Policy')
  end
end

Also applies to: 226-314

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a2b6be6 and 82833a9.

📒 Files selected for processing (42)
  • lib/mailtrap/project.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/returns_an_array_of_EmailTemplate_objects.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (3 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/returns_an_EmailTemplate_object.yml (0 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/mailtrap/projects_api_spec.rb (1 hunks)
  • spec/spec_helper.rb (1 hunks)
💤 Files with no reviewable changes (9)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/returns_an_array_of_EmailTemplate_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/returns_an_EmailTemplate_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/returns_an_EmailTemplate_object.yml
✅ Files skipped from review due to trivial changes (4)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml
🚧 Files skipped from review as they are similar to previous changes (8)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml
  • spec/mailtrap/projects_api_spec.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml
  • lib/mailtrap/project.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml
🧰 Additional context used
🧬 Code Graph Analysis (1)
spec/spec_helper.rb (1)
lib/mailtrap/mail/base.rb (1)
  • to_json (64-69)
🔇 Additional comments (36)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1)

16-19: Good redaction of sensitive values.

Authorization is masked and share_links tokens are properly redacted to /share/REDACTED. Looks safe to commit.

Also applies to: 164-165

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (2)

163-166: Share link tokens correctly sanitized in both interactions.

Both the POST and PATCH responses redact tokens under share_links consistently. Nice.

Also applies to: 326-328


4-6: Keep URIs stable across environments.

URIs use /accounts/ACCOUNT_ID/, which relies on the test env using that literal value. If a contributor sets MAILTRAP_ACCOUNT_ID to a numeric ID and re-records, matching may fail unless the matcher normalizes the account segment. See my spec_helper.rb comment for a robust matcher to avoid env coupling.

Also applies to: 167-169

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1)

163-166: PII/sensitive data handling looks correct.

Bearer token placeholder is present and share_links are redacted. Permissions/inboxes fields provide good fixture coverage.

Also applies to: 326-329

spec/spec_helper.rb (1)

24-44: All cassettes are properly sanitized—no residual share tokens or raw account IDs detected.
Running the provided regex checks against spec/fixtures/vcr_cassettes yielded no matches, confirming that sensitive data has been redacted as intended. ✅

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (1)

5-6: Account ID placeholder and token masking look correct.

URIs are parameterized with ACCOUNT_ID and the Authorization header is masked. Fixture is safe to ship.

Also applies to: 27-36, 150-162, 193-201

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1)

5-5: Good: endpoint generalized with ACCOUNT_ID placeholder.

This de-couples fixtures from real account numbers and aligns with the new redaction scheme.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (2)

5-5: Good: POST URI uses ACCOUNT_ID placeholder.

Keeps fixtures portable across environments.


165-168: No unintended hardcoded IDs/UUIDs detected—pinning is intentional for VCR fixtures

I’ve searched the spec/fixtures/vcr_cassettes directory and confirmed that all numeric IDs and UUIDs (including the new id: 45464 and uuid: "776e093c-e707-425d-a09d-5f311c9201e5") are consistently pinned across the EmailTemplates and Projects API cassettes. These values are used to assert precise mapping behavior in your specs, so recording them exactly is intentional and necessary for the tests to pass.

No further changes are required here.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (1)

5-5: Good: Both create and patch endpoints parameterized with ACCOUNT_ID.

Also applies to: 171-171

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (1)

5-5: Good: POST URI generalized with ACCOUNT_ID.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (3)

5-5: Good: create and delete URIs now use ACCOUNT_ID.

Also applies to: 171-171


5-5: No numeric account IDs found in VCR fixtures

Ran a repository‐wide ripgrep search for accounts/\d+ under spec/fixtures/vcr_cassettes and confirmed there are no leftover numeric IDs—only the ACCOUNT_ID placeholder remains. All fixtures are up to date.


321-323: 204 No Content Handling Verified

The handle_response method in lib/mailtrap/client.rb explicitly matches Net::HTTPNoContent and returns nil before any JSON parsing takes place. DELETE requests yielding a 204 status are therefore treated as a successful no-content response, and won’t trigger json_response on an empty body. No changes needed.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (1)

5-5: Good move: parameterized ACCOUNT_ID in request URI.

Replacing hard-coded account IDs with ACCOUNT_ID improves secrecy and cassette reusability.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (2)

5-5: ACCOUNT_ID parameterization is consistent across create and patch.

URI redaction is applied to both POST and PATCH endpoints. Looks good.

Also applies to: 171-171


165-168: Data consistency check: PATCH targets created resource and preserves other fields.

The PATCH operates on id 45471 (same as created), updates only body_html, and preserves name/subject/category/body_text, matching the test case intent.

Optionally assert this at spec level to prevent regressions:

expect(updated.body_text).to eq(original.body_text)
expect(updated.subject).to eq(original.subject)
expect(updated.category).to eq(original.category)

Also applies to: 329-333

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (2)

5-5: ACCOUNT_ID placeholder used consistently.

Good redaction and consistency across create and patch URIs.

Also applies to: 171-171


165-168: Semantics preserved: only category changes.

PATCH response reflects category update while keeping other fields unchanged. Matches scenario name.

Also applies to: 329-333

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (2)

5-5: ACCOUNT_ID path redaction: consistent and correct.

Creation and update URIs use the placeholder; good.

Also applies to: 171-171


165-168: Validation: targeted fields updated, others preserved.

PATCH updates category and body_html; name/subject/body_text remain, consistent with test intent.

Consider asserting that exactly the intended fields changed:

expect(updated.attributes.slice('category','body_html'))
  .to eq('category' => 'Updated Category', 'body_html' => '<div>Updated HTML</div>')

Also applies to: 330-334

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (2)

5-5: ACCOUNT_ID placeholder is correctly used in POST and DELETE URIs.

Redaction is consistent for both setup and the not-found DELETE.

Also applies to: 171-171


165-168: DELETE not-found scenario captured as JSON error; OK.

404 with {"error":"Not Found"} body is recorded; aligns with client error handling expectations.

Also applies to: 327-329

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (3)

5-5: Good: account id parameterized in URI

Using ACCOUNT_ID in the request path avoids leaking real IDs and stabilizes cassettes across environments.


164-165: Sanity check: recorded_at aligns with response Date

Recorded timestamp matches the response Date header (Wed, 20 Aug 2025 19:39:38 GMT). Looks consistent.


5-5: Redact dynamic share_links URLs in VCR cassettes

The verification confirms that there are no hard-coded Mailtrap account IDs and all Bearer tokens use placeholders, but multiple fixtures still include unredacted share_links entries. These dynamic URLs must be masked or replaced with placeholders to avoid leaking internal IDs.

• Affected fixtures under spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/**
– e.g. _update/maps_response_data_to_Project_object.yml (lines 164, 327)
_get/maps_response_data_to_Project_object.yml (lines 164, 327)
_delete/returns_deleted_project_id.yml (line 164)
– plus many others in the Mailtrap_ProjectsAPI/_list/ directory

Suggested replacement (apply across all occurrences):

- "share_links":{"admin":"https://mailtrap.io/projects/2487014/share/REDACTED","viewer":"https://mailtrap.io/projects/2487014/share/REDACTED"}
+ "share_links":{"admin":"<SHARE_LINK>","viewer":"<SHARE_LINK>"}

Please update or remove all share_links values to use <SHARE_LINK> (or another agreed placeholder) throughout the VCR cassettes.

Likely an incorrect or invalid review comment.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (2)

5-5: Good: consistent ACCOUNT_ID usage and resource id continuity

URI uses ACCOUNT_ID; PATCH path references the id returned by the POST (45466). That consistency keeps the scenario realistic and stable.

Also applies to: 171-171


165-168: Temporal consistency looks correct

created_at/updated_at values advance logically between create and update, matching the recorded_at values. No issues spotted.

Also applies to: 331-334

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (2)

171-175: Nice: PATCH updates only the intended field

Request body includes only category, and the response reflects only category changing while name/subject remain intact. This is a solid, focused fixture.

Also applies to: 329-333


5-5: URI parameterization and id continuity look good

Both POST and PATCH use ACCOUNT_ID; PATCH path matches the created id (45468).

Also applies to: 171-171

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (1)

5-5: Good: ACCOUNT_ID paths and concise PATCH body

URI parameterization is consistent. PATCH updates only selected fields (name/subject/category), leaving HTML/text intact, which mirrors real partial updates well.

Also applies to: 171-175

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (1)

5-5: ACCOUNT_ID parameterization and id reuse are correct

The GET targets the id returned by the preceding POST (45462). This makes the create→get flow deterministic.

Also applies to: 171-172

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (4)

5-5: Good: ACCOUNT_ID is parameterized in URIs

Using ACCOUNT_ID placeholders in both POST and PATCH endpoints removes hard-coded tenant data and aligns with the new redaction policy.

Also applies to: 171-171


165-168: Behavioral check: only body_html changes; other fields remain intact

The POST response shows the original body_html, and the PATCH response updates only body_html while preserving name, category, subject, and body_text. This matches the scenario name and looks correct.

If not already covered, ensure the spec asserts that:

  • body_html changes to "
    New HTML Only
    "
  • name, category, subject, and body_text remain unchanged
  • updated_at changes while created_at stays the same

Also applies to: 329-333


164-165: AI summary inconsistency: no ASCII-8BIT → UTF-8 shift present here

Both recorded bodies declare encoding: UTF-8. There’s no ASCII-8BIT to UTF-8 change in this cassette. The high-level summary appears slightly off for this file.

Also applies to: 329-330


171-171: No action required: IDs and placeholders are consistent

All fixtures in the when_updating_only_body_html scenario use the correct template IDs and placeholders:

  • updates_only_the_body_html_field.yml consistently references 45470 in the URI and both request/response bodies.
  • preserves_other_fields_unchanged.yml consistently references 45471 in its URI and bodies.
  • No hard-coded numeric account IDs remain (all use ACCOUNT_ID), and all Authorization headers are masked as <BEARER_TOKEN>.

Comment on lines +171 to 172
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates/999999
body:
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm VCR request matching works with ACCOUNT_ID placeholder.

Recording replaces the numeric account in the cassette with ACCOUNT_ID. Ensure playback uses a custom matcher that normalizes /accounts/ to /accounts/ACCOUNT_ID, otherwise matches will fail.

Add a custom matcher in spec/spec_helper.rb:

 VCR.configure do |c|
+  c.register_request_matcher :normalized_uri do |r1, r2|
+    norm = ->(u) { u.gsub(%r{/accounts/\d+}, '/accounts/ACCOUNT_ID') }
+    norm.call(r1.uri) == norm.call(r2.uri)
+  end
+  c.default_cassette_options = { match_requests_on: [:method, :normalized_uri, :body] }
 end

Run to verify the matcher exists (or add it if missing):


🏁 Script executed:

#!/usr/bin/env bash
rg -nC2 "register_request_matcher\s*:normalized_uri|/accounts/\d+|ACCOUNT_ID" spec/spec_helper.rb

Length of output: 244


Add a VCR custom matcher to normalize the ACCOUNT_ID placeholder

Your spec/spec_helper.rb currently filters out the real account ID (via config.filter_sensitive_data('ACCOUNT_ID')), but there is no request matcher to normalize URIs during playback. Without it, recordings using ACCOUNT_ID will not match real requests against the cassette’s URI pattern.

Please add the following inside your VCR configuration block in spec/spec_helper.rb:

 VCR.configure do |c|
   # existing configuration...
+  # Normalize /accounts/<digits> to /accounts/ACCOUNT_ID for playback matching
+  c.register_request_matcher :normalized_uri do |r1, r2|
+    normalize = ->(uri) { uri.gsub(%r{/accounts/\d+}, '/accounts/ACCOUNT_ID') }
+    normalize.call(r1.uri) == normalize.call(r2.uri)
+  end
+  c.default_cassette_options = {
+    match_requests_on: [:method, :normalized_uri, :body]
+  }
 end

• Location: spec/spec_helper.rb within the VCR.configure do |c| … end block
• Purpose: Ensure recorded URIs with a numeric account ID (e.g. /accounts/12345) match playback URIs using the placeholder /accounts/ACCOUNT_ID.

📝 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
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates/999999
body:
VCR.configure do |c|
# existing configuration...
# Normalize /accounts/<digits> to /accounts/ACCOUNT_ID for playback matching
c.register_request_matcher :normalized_uri do |r1, r2|
normalize = ->(uri) { uri.gsub(%r{/accounts/\d+}, '/accounts/ACCOUNT_ID') }
normalize.call(r1.uri) == normalize.call(r2.uri)
end
c.default_cassette_options = {
match_requests_on: [:method, :normalized_uri, :body]
}
end
🤖 Prompt for AI Agents
In spec/spec_helper.rb around the VCR.configure do |c| … end block (add near
existing filter_sensitive_data), add a custom request matcher that normalizes
account IDs in URIs by replacing any /accounts/<digits> segment with
/accounts/ACCOUNT_ID before comparing; register it with
c.register_request_matcher (e.g. :uri_without_account_id) and then include that
matcher in VCR’s match_requests_on (or in
default_cassette_options[:match_requests_on]) so recorded URIs with numeric
account IDs will match playback URIs using the ACCOUNT_ID placeholder.

Comment on lines +24 to +25
config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden ACCOUNT_ID redaction and avoid KeyError when env is missing.

ENV.fetch will raise if MAILTRAP_ACCOUNT_ID is not set, breaking playback for contributors who only use existing cassettes. Also, tying cassettes to the literal "ACCOUNT_ID" string makes local runs fragile.

Apply:

-  config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
+  # Replace the actual account id wherever it appears; fall back to the placeholder
+  config.filter_sensitive_data('ACCOUNT_ID') { ENV['MAILTRAP_ACCOUNT_ID'] || 'ACCOUNT_ID' }
📝 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
config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
# Replace the actual account id wherever it appears; fall back to the placeholder
config.filter_sensitive_data('ACCOUNT_ID') { ENV['MAILTRAP_ACCOUNT_ID'] || 'ACCOUNT_ID' }
🤖 Prompt for AI Agents
In spec/spec_helper.rb around lines 24-25, replace the ENV.fetch usage with a
safe lookup that falls back to a stable placeholder so it never raises (e.g.
account_id = ENV['MAILTRAP_ACCOUNT_ID'] || 'ACCOUNT_ID_PLACEHOLDER') and pass
that variable into config.filter_sensitive_data so VCR will redact the real
value when present and still match/playback using the placeholder when the env
var is missing; this avoids KeyError and decouples cassettes from a missing
literal env value.

Copy link
Contributor

@IgorDobryn IgorDobryn left a comment

Choose a reason for hiding this comment

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

It was quite difficult to review. Mostly because there are primary changes + cassettes cleanup. I've reviewed this time, but next time I'd suggest to split changes into smaller PR's. I.e. in this case it would be good to submit VCR changes and then projects API on top of it.

Other than that, I'd suggest some minor cleanup in specs.

end
end

config.filter_sensitive_data('ACCOUNT_ID') { ENV.fetch('MAILTRAP_ACCOUNT_ID') }
Copy link
Contributor

Choose a reason for hiding this comment

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

This can cause false positives to replace account ids in the cassette while it's not actually an account id

Copy link
Author

Choose a reason for hiding this comment

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

even if it happens, there is no harm in it, it's just removed from saved cassette content.

Copy link
Contributor

@i7an i7an left a comment

Choose a reason for hiding this comment

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

  • Fix rubocop
  • All tests are failing. See code rabbit review comments.
      KeyError:
        key not found: "MAILTRAP_ACCOUNT_ID"

@IgorDobryn IgorDobryn marked this pull request as draft October 20, 2025 08:46
@jurgens jurgens marked this pull request as ready for review November 3, 2025 17:06
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: 2

♻️ Duplicate comments (5)
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (1)

5-5: Verify VCR request matcher configuration for ACCOUNT_ID placeholder.

This cassette uses literal ACCOUNT_ID placeholders in URIs (lines 5 and 171), but runtime requests will contain numeric account IDs. Without a custom VCR matcher, cassette playback will fail on URI mismatch.

A previous review flagged this and suggested registering a custom matcher in spec/spec_helper.rb to normalize /accounts/<numeric_id>/… to /accounts/ACCOUNT_ID/… before comparison. Ensure this matcher has been implemented.

#!/bin/bash
# Verify that spec/spec_helper.rb includes a custom VCR matcher for account ID normalization
rg -A 10 'register_request_matcher|normalized.*uri|account.*id' spec/spec_helper.rb

Also applies to: 171-171

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (1)

5-5: Verify custom VCR matcher is configured to normalize ACCOUNT_ID placeholders.

This cassette now uses the ACCOUNT_ID placeholder in URIs (lines 5, 171). However, VCR matches requests by literal URI during playback. Without a custom request matcher in spec/spec_helper.rb that normalizes /accounts/<digits> to /accounts/ACCOUNT_ID, recorded cassettes won't match runtime requests with numeric account IDs.

Ensure that spec/spec_helper.rb includes the custom matcher suggested in previous review feedback:

VCR.configure do |c|
  c.register_request_matcher :normalized_uri do |r1, r2|
    normalize = ->(uri) { uri.gsub(%r{/accounts/\d+}, '/accounts/ACCOUNT_ID') }
    normalize.call(r1.uri) == normalize.call(r2.uri)
  end
  c.default_cassette_options = {
    match_requests_on: [:method, :normalized_uri, :body, ...]
  }
end

Run the following to confirm the matcher is registered:

#!/bin/bash
# Verify that spec/spec_helper.rb includes a custom request matcher for account ID normalization
rg -nC5 'register_request_matcher.*normalized_uri|register_request_matcher.*uri_without_account_id' spec/spec_helper.rb

Also applies to: 171-171

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (1)

1-335: VCR cassette properly redacts sensitive data.

The fixture correctly uses ACCOUNT_ID and <BEARER_TOKEN> placeholders. The hard-coded resource IDs and timestamps in response bodies have been previously discussed.

spec/mailtrap/projects_api_spec.rb (2)

76-89: Subject override still needed to exercise positional Hash path

This context continues to call project_api.create(**request) via the outer subject, so it never exercises the intended positional-Hash call shape. Please override subject(:create) inside this context to call project_api.create(request) directly, matching the earlier feedback.

 context 'with hash request' do
+  subject(:create) { project_api.create(request) }
   let(:request) do
     {
       name: 'New Project'
     }
   end

129-142: Likewise here—override subject to hit the Hash code path

Same situation: the nested context still relies on the outer subject(:update) which splats into keyword args. Override the subject locally so the spec actually calls project_api.update(project_id, request) and covers the positional-Hash branch we want to keep working.

 context 'with hash request' do
+  subject(:update) { project_api.update(project_id, request) }
   let(:request) do
     {
       name: 'Updated Project'
     }
   end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 225484a and ba37008.

📒 Files selected for processing (37)
  • lib/mailtrap.rb (1 hunks)
  • lib/mailtrap/client.rb (0 hunks)
  • lib/mailtrap/project.rb (1 hunks)
  • lib/mailtrap/projects_api.rb (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/maps_response_data_to_EmailTemplate_object.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_create/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (4 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_get/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (3 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_template_does_not_exist/raises_not_found_error.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/updates_only_the_body_html_field.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/preserves_other_fields_unchanged.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_category/updates_only_the_category_field.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (7 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/mailtrap/project_spec.rb (1 hunks)
  • spec/mailtrap/projects_api_spec.rb (1 hunks)
  • spec/spec_helper.rb (1 hunks)
💤 Files with no reviewable changes (1)
  • lib/mailtrap/client.rb
🚧 Files skipped from review as they are similar to previous changes (12)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml
  • lib/mailtrap.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml
  • lib/mailtrap/project.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml
  • lib/mailtrap/projects_api.rb
🧰 Additional context used
🧬 Code graph analysis (3)
spec/spec_helper.rb (1)
lib/mailtrap/mail/base.rb (1)
  • to_json (64-69)
spec/mailtrap/projects_api_spec.rb (1)
lib/mailtrap/projects_api.rb (6)
  • list (17-19)
  • include (7-66)
  • get (25-27)
  • create (35-37)
  • update (45-47)
  • delete (53-55)
spec/mailtrap/project_spec.rb (1)
lib/mailtrap/project.rb (1)
  • to_h (20-22)
🔇 Additional comments (10)
spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/with_hash_request/maps_response_data_to_EmailTemplate_object.yml (1)

1-335: Clarify the scope of this PR—EmailTemplatesAPI fixture changes don't align with "add projects API" objective.

This VCR fixture file documents recorded HTTP interactions for EmailTemplatesAPI._update tests. However, the PR description states the objective is to add ProjectsAPI support, not to modify existing EmailTemplatesAPI fixtures.

The changes here include:

  • URI parameterization of ACCOUNT_ID (lines 5, 171) – a good infrastructure improvement for fixture maintainability
  • CSP header expansion with hundreds of Google domain entries (lines 70–108, 109–144, 236–271, 275–310)
  • Response data regeneration with updated IDs, UUIDs, and timestamps

Questions:

  1. Is this fixture regeneration incidental to broader infrastructure changes (e.g., VCR redaction updates mentioned in the summary), or is it intended to be part of the projects API PR?
  2. Are the CSP header expansions legitimate changes from the API itself, or are they the result of a separate test infrastructure update?

If these changes are orthogonal to the projects API work, consider separating them into a dedicated infrastructure/test-fixture PR for clearer review scope.

if fixture regeneration is automatically managed by VCR and not intended for manual review.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_delete/returns_no_content.yml (1)

27-27: Cassette fixture appears properly refreshed with updated metadata.

The metadata updates (timestamps, request IDs, CSP nonce values, response body IDs) appear consistent with a normal fixture re-record. The fixture structure is valid and the ACCOUNT_ID placeholders are in place.

Also applies to: 35-35, 55-55, 57-57, 150-150, 152-152, 154-154, 160-160, 165-167, 192-192, 196-196, 214-214, 307-307, 309-309, 311-311, 317-317

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1)

1-6: Pending verification: Confirm VCR custom matcher implementation for ACCOUNT_ID normalization.

The cassette uses the ACCOUNT_ID placeholder at line 5, but VCR's default :uri matcher cannot handle this. A previous review flagged that without a custom request matcher in spec/spec_helper.rb, cassette playback will fail when ENV['MAILTRAP_ACCOUNT_ID'] differs from the literal "ACCOUNT_ID" string.

Verify that the fix was applied (custom matcher registered to normalize the /accounts/:id segment during matching). This is a prerequisite for all ACCOUNT_ID-parameterized cassettes.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml (1)

1-327: Well-structured error scenario test.

The cassette properly tests the 404 Not Found path with appropriate setup and redaction of sensitive data.

spec/mailtrap/project_spec.rb (1)

1-138: Comprehensive test coverage for Project struct.

The tests thoroughly validate initialization and the to_h method, including the edge case of nil attributes being compacted out. Good use of RSpec's have_attributes and clear test descriptions.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1)

1-166: Properly configured VCR cassette for project creation.

The fixture correctly captures the create flow with appropriate sensitive data redaction.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1)

1-166: Authorization error scenario properly captured.

The cassette correctly tests the 401 Unauthorized path with appropriate error response structure.

spec/spec_helper.rb (1)

24-24: ENV.fetch is appropriate for required configuration.

Using ENV.fetch ensures tests fail fast if MAILTRAP_ACCOUNT_ID is not set, which helps catch configuration issues early.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_only_body_html/preserves_other_fields_unchanged.yml (1)

1-334: Cassette follows established patterns.

Properly redacts sensitive data and tests the partial update scenario.

spec/fixtures/vcr_cassettes/Mailtrap_EmailTemplatesAPI/_update/when_updating_multiple_specific_fields/updates_only_the_specified_fields.yml (1)

1-335: Multi-field update scenario properly tested.

The cassette correctly captures the behavior of updating multiple fields simultaneously.

- request:
method: post
uri: https://mailtrap.io/api/accounts/1111111/email_templates
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/email_templates
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify custom URI matcher is implemented in spec_helper.rb.

Both POST (line 5) and DELETE (line 171) URIs correctly use the ACCOUNT_ID placeholder. However, for these cassettes to match real requests during playback, a custom request matcher must normalize the dynamic account segment in spec_helper.rb. Without it, test playback will fail when the actual account ID differs from the placeholder.

Please confirm that spec/spec_helper.rb contains a custom matcher (e.g., :uri_with_account_placeholder) registered in the VCR.configure block that normalizes URIs by substituting /accounts/<any> with /accounts/ACCOUNT_ID before comparing, and that this matcher is included in default_cassette_options[:match_requests_on].

Run the following script to verify:

Also applies to: 171-171


🏁 Script executed:

#!/bin/bash
# Check that the custom URI matcher is implemented in spec_helper.rb

echo "=== Searching for register_request_matcher in spec_helper.rb ==="
rg -nC5 'register_request_matcher.*uri_with_account_placeholder' spec/spec_helper.rb || echo "No matcher found"

echo ""
echo "=== Checking default_cassette_options includes the custom matcher ==="
rg -nA10 'default_cassette_options' spec/spec_helper.rb | head -20

Length of output: 486


Implement custom URI matcher in spec_helper.rb to normalize account ID placeholders.

Verification confirms the issue: no custom :uri_with_account_placeholder matcher exists in spec_helper.rb, and the default_cassette_options (line 59) only includes [:method, :uri, :body, header_matcher].

VCR's :uri matcher performs exact string comparison. The cassettes use /accounts/ACCOUNT_ID/ but real requests will have actual account IDs (e.g., /accounts/12345/), causing VCR to fail matching requests during playback and tests to fail.

Required fix:
Add a custom request matcher in spec_helper.rb that normalizes URIs by replacing /accounts/<any_id> with /accounts/ACCOUNT_ID, then register it in default_cassette_options[:match_requests_on].

🤖 Prompt for AI Agents
In spec/spec_helper.rb around line 59, VCR's matcher list uses the default :uri
which compares exact strings; add a custom matcher (e.g.,
:uri_with_account_placeholder) that normalizes request URIs by replacing any
/accounts/<any_id> segment with /accounts/ACCOUNT_ID before comparing, register
that matcher in VCR.configure.register_request_matcher, and update
default_cassette_options[:match_requests_on] to use
:uri_with_account_placeholder in place of :uri so recorded cassettes with
ACCOUNT_ID placeholders match real requests.

Comment on lines +26 to +48
config.before_record do |interaction|
body = JSON.parse(interaction.response.body)

case body
when Hash
body['share_links'].transform_values! { |e| e.gsub(%r{/share/.+}, '/share/REDACTED') } if body.key?('share_links')
when Array
body.map do |item|
if item.key?('share_links')
item['share_links'].transform_values! do |e|
e.gsub(%r{/share/.+}, '/share/REDACTED')
end
end
item
end
else # rubocop:disable Style/EmptyElse
# noop
end

interaction.response.body = body.to_json
rescue JSON::ParserError
# do nothing
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Harden the share_links redaction to prevent runtime errors.

The before_record hook has several robustness issues:

  1. Line 27: JSON.parse will raise if interaction.response.body is nil or empty
  2. Line 32: body.map is used but the result is discarded; use body.each instead
  3. Lines 31, 36: The regex %r{/share/.+} is greedy and matches to the end of the string; use %r{/share/[^"}\s]+} or similar
  4. Lines 31, 35: No validation that share_links is a Hash before calling transform_values!

Apply this diff to add defensive checks:

   config.before_record do |interaction|
-    body = JSON.parse(interaction.response.body)
+    raw_body = interaction.response.body
+    return if raw_body.nil? || raw_body.empty?
+
+    body = JSON.parse(raw_body)
 
     case body
     when Hash
-      body['share_links'].transform_values! { |e| e.gsub(%r{/share/.+}, '/share/REDACTED') } if body.key?('share_links')
+      if body.key?('share_links') && body['share_links'].is_a?(Hash)
+        body['share_links'].transform_values! { |e| e.gsub(%r{/share/[^"}\s]+}, '/share/REDACTED') }
+      end
     when Array
-      body.map do |item|
-        if item.key?('share_links')
-          item['share_links'].transform_values! do |e|
-            e.gsub(%r{/share/.+}, '/share/REDACTED')
+      body.each do |item|
+        if item.is_a?(Hash) && item.key?('share_links') && item['share_links'].is_a?(Hash)
+          item['share_links'].transform_values! do |e|
+            e.gsub(%r{/share/[^"}\s]+}, '/share/REDACTED')
           end
         end
-        item
       end
     else # rubocop:disable Style/EmptyElse
       # noop
     end
 
     interaction.response.body = body.to_json
   rescue JSON::ParserError
     # do nothing
   end
🤖 Prompt for AI Agents
In spec/spec_helper.rb around lines 26 to 48, harden the before_record hook:
guard against nil/empty interaction.response.body before calling JSON.parse
(skip parsing if nil/empty), change body.map to body.each when iterating arrays
so results aren't discarded, validate that any share_links value is a Hash
before calling transform_values! (only call transform_values! on a Hash), and
replace the greedy regex %r{/share/.+} with a safer pattern like
%r{/share/[^"\}\s]+} to avoid matching to the end of the string; ensure
interaction.response.body is only overwritten with body.to_json after successful
parsing.

@jurgens
Copy link
Author

jurgens commented Nov 6, 2025

@IgorDobryn @i7an guys, i believe it's ready for a merge
conflicts resolved and tests are green

@jurgens jurgens requested review from IgorDobryn and i7an November 6, 2025 17:31
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 (1)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1)

165-398: Consider optimizing fixture size and test coverage clarity.

The fixture is quite large (~100+ projects) with many appearing to be near-duplicate test entries (e.g., multiple "Test Project", "New Project", "Updated Project", "Original Project", "Project to Delete" variations). This can slow down test runs and make maintenance harder.

Consider whether this level of data duplication is necessary for the test coverage, or if a smaller, more focused fixture (perhaps 3–5 representative project entries) would be sufficient to validate the core mapping logic. This would improve test performance and readability without sacrificing coverage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba37008 and 0e67855.

📒 Files selected for processing (15)
  • .gitignore (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml (1 hunks)
  • spec/fixtures/vcr_cassettes/Mailtrap_SuppressionsAPI/vcr_list/when_api_key_is_incorrect/raises_authorization_error.yml (5 hunks)
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • spec/fixtures/vcr_cassettes/Mailtrap_SuppressionsAPI/vcr_list/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.yml
🚧 Files skipped from review as they are similar to previous changes (7)
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/when_api_key_is_incorrect/raises_authorization_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/maps_response_data_to_Project_object.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_get/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_updating_only_name/updates_only_the_name_field.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/when_project_does_not_exist/raises_not_found_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/when_project_does_not_exist/raises_not_found_error.yml
🔇 Additional comments (4)
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_list/maps_response_data_to_Project_objects.yml (1)

1-25: Verify test data privacy and compliance.

The fixture contains real-looking email addresses (e.g., "[email protected]", "[email protected]", "[email protected]") in the response body. If these represent actual PII rather than anonymized test data, they should be redacted or replaced with test-only email addresses (e.g., [email protected] or hashed values) to protect user privacy and maintain GDPR/privacy compliance.

The authorization token (line 19) and account ID (line 5) are correctly redacted, and share links are properly masked (line 167+), so the redaction infrastructure is working—just ensure email addresses are handled consistently with your data privacy policy.

Can you confirm whether these email addresses are synthetic test data or represent real users, and whether they should be redacted per your privacy/security guidelines?

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_create/with_hash_request/maps_response_data_to_Project_object.yml (1)

1-168: Cassette file is correctly redacted; clarify PR description mentioning two HTTP interactions.

Redaction patterns in spec/spec_helper.rb align perfectly with the cassette:

  • Bearer token redacted to <BEARER_TOKEN>
  • Account ID redacted to ACCOUNT_ID from environment variable ✓
  • Share links redacted to /share/REDACTED pattern ✓

However, the cassette contains only one POST interaction, not two. The PR description states it "records two HTTP interactions (POST to create a project and PATCH to update)" but no PATCH interaction exists in the file. Confirm whether this was intentional or if the PR description needs updating.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_delete/returns_deleted_project_id.yml (1)

1-333: VCR fixture redaction is consistent and secure across all Projects API cassettes.

Verification confirms that all 13 ProjectsAPI cassettes consistently implement proper redaction: bearer tokens masked with <BEARER_TOKEN> placeholders, share links marked REDACTED, and account IDs replaced with ACCOUNT_ID placeholders. No unredacted sensitive data found. HTTP interactions follow standard REST patterns with appropriate status codes and response structures.

spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/maps_response_data_to_Project_object.yml (1)

1-333: VCR cassette fixture is properly configured.

Sensitive data is appropriately redacted (Bearer tokens masked, ACCOUNT_ID placeholders, share_links redacted). The fixture correctly records two HTTP interactions for create-then-update flow, supporting the Projects API test suite.

Comment on lines +1 to +331
---
http_interactions:
- request:
method: post
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/projects
body:
encoding: UTF-8
string: '{"project":{"name":"Original Project"}}'
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- mailtrap-ruby (https://github.com/mailtrap/mailtrap-ruby)
Host:
- mailtrap.io
Authorization:
- Bearer <BEARER_TOKEN>
Content-Type:
- application/json
response:
status:
code: 200
message: OK
headers:
Date:
- Thu, 06 Nov 2025 17:22:49 GMT
Content-Type:
- application/json; charset=utf-8
Transfer-Encoding:
- chunked
Connection:
- keep-alive
Server:
- cloudflare
X-Frame-Options:
- SAMEORIGIN
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Permitted-Cross-Domain-Policies:
- none
Referrer-Policy:
- strict-origin-when-cross-origin
Vary:
- Accept
X-Mailtrap-Version:
- v2
X-Ratelimit-Limit:
- '150'
X-Ratelimit-Remaining:
- '139'
Etag:
- W/"76642706d6a6767af3d369475a2c93bf"
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'default-src ''self''; style-src ''self'' data: blob: ''unsafe-inline'' assets.mailtrap.io
www.googletagmanager.com fonts.googleapis.com; font-src ''self'' data: blob:
''unsafe-inline'' assets.mailtrap.io static.hsappstatic.net fonts.gstatic.cn
fonts.gstatic.com *.s-microsoft.com use.typekit.net; script-src ''self'' data:
blob: assets.mailtrap.io *.cookiebot.com www.clarity.ms *.doubleclick.net
*.googlesyndication.com *.googletagmanager.com www.googleadservices.com www.google.com
beacon-v2.helpscout.net js.hs-analytics.net js.hs-banner.com js.hs-scripts.com
cdn.firstpromoter.com connect.facebook.net www.recaptcha.net www.gstatic.cn
www.gstatic.com *.quora.com static.ads-twitter.com snap.licdn.com *.growthbook.io
translate.google.com ''nonce-js-SmgW+50Ja0qu+p4Pg6QILQ==''; connect-src ''self''
assets.mailtrap.io wss://mailtrap.io *.google.com *.google.ad *.google.ae
*.google.com.af *.google.com.ag *.google.al *.google.am *.google.co.ao *.google.com.ar
*.google.as *.google.at *.google.com.au *.google.az *.google.ba *.google.com.bd
*.google.be *.google.bf *.google.bg *.google.com.bh *.google.bi *.google.bj
*.google.com.bn *.google.com.bo *.google.com.br *.google.bs *.google.bt *.google.co.bw
*.google.by *.google.com.bz *.google.ca *.google.cd *.google.cf *.google.cg
*.google.ch *.google.ci *.google.co.ck *.google.cl *.google.cm *.google.cn
*.google.com.co *.google.co.cr *.google.com.cu *.google.cv *.google.com.cy
*.google.cz *.google.de *.google.dj *.google.dk *.google.dm *.google.com.do
*.google.dz *.google.com.ec *.google.ee *.google.com.eg *.google.es *.google.com.et
*.google.fi *.google.com.fj *.google.fm *.google.fr *.google.ga *.google.ge
*.google.gg *.google.com.gh *.google.com.gi *.google.gl *.google.gm *.google.gr
*.google.com.gt *.google.gy *.google.com.hk *.google.hn *.google.hr *.google.ht
*.google.hu *.google.co.id *.google.ie *.google.co.il *.google.im *.google.co.in
*.google.iq *.google.is *.google.it *.google.je *.google.com.jm *.google.jo
*.google.co.jp *.google.co.ke *.google.com.kh *.google.ki *.google.kg *.google.co.kr
*.google.com.kw *.google.kz *.google.la *.google.com.lb *.google.li *.google.lk
*.google.co.ls *.google.lt *.google.lu *.google.lv *.google.com.ly *.google.co.ma
*.google.md *.google.me *.google.mg *.google.mk *.google.ml *.google.com.mm
*.google.mn *.google.com.mt *.google.mu *.google.mv *.google.mw *.google.com.mx
*.google.com.my *.google.co.mz *.google.com.na *.google.com.ng *.google.com.ni
*.google.ne *.google.nl *.google.no *.google.com.np *.google.nr *.google.nu
*.google.co.nz *.google.com.om *.google.com.pa *.google.com.pe *.google.com.pg
*.google.com.ph *.google.com.pk *.google.pl *.google.pn *.google.com.pr *.google.ps
*.google.pt *.google.com.py *.google.com.qa *.google.ro *.google.ru *.google.rw
*.google.com.sa *.google.com.sb *.google.sc *.google.se *.google.com.sg *.google.sh
*.google.si *.google.sk *.google.com.sl *.google.sn *.google.so *.google.sm
*.google.sr *.google.st *.google.com.sv *.google.td *.google.tg *.google.co.th
*.google.com.tj *.google.tl *.google.tm *.google.tn *.google.to *.google.com.tr
*.google.tt *.google.com.tw *.google.co.tz *.google.com.ua *.google.co.ug
*.google.co.uk *.google.com.uy *.google.co.uz *.google.com.vc *.google.co.ve
*.google.co.vi *.google.com.vn *.google.vu *.google.ws *.google.rs *.google.co.za
*.google.co.zm *.google.co.zw *.google.cat errors.rw.rw *.cookiebot.com *.clarity.ms
*.g.doubleclick.net *.googlesyndication.com *.googletagmanager.com www.google.com
wss://ws-helpscout.pusher.com sockjs-helpscout.pusher.com *.helpscout.net
*.firstpromoter.com connect.facebook.net *.facebook.com www.recaptcha.net
*.analytics.google.com *.google-analytics.com *.quora.com *.linkedin.com analytics.twitter.com
t.co/1/i/adsct *.growthbook.io meta-gateway.mailtrap.io translate-pa.googleapis.com;
img-src ''self'' data: blob: assets.mailtrap.io *.google.com *.google.ad *.google.ae
*.google.com.af *.google.com.ag *.google.al *.google.am *.google.co.ao *.google.com.ar
*.google.as *.google.at *.google.com.au *.google.az *.google.ba *.google.com.bd
*.google.be *.google.bf *.google.bg *.google.com.bh *.google.bi *.google.bj
*.google.com.bn *.google.com.bo *.google.com.br *.google.bs *.google.bt *.google.co.bw
*.google.by *.google.com.bz *.google.ca *.google.cd *.google.cf *.google.cg
*.google.ch *.google.ci *.google.co.ck *.google.cl *.google.cm *.google.cn
*.google.com.co *.google.co.cr *.google.com.cu *.google.cv *.google.com.cy
*.google.cz *.google.de *.google.dj *.google.dk *.google.dm *.google.com.do
*.google.dz *.google.com.ec *.google.ee *.google.com.eg *.google.es *.google.com.et
*.google.fi *.google.com.fj *.google.fm *.google.fr *.google.ga *.google.ge
*.google.gg *.google.com.gh *.google.com.gi *.google.gl *.google.gm *.google.gr
*.google.com.gt *.google.gy *.google.com.hk *.google.hn *.google.hr *.google.ht
*.google.hu *.google.co.id *.google.ie *.google.co.il *.google.im *.google.co.in
*.google.iq *.google.is *.google.it *.google.je *.google.com.jm *.google.jo
*.google.co.jp *.google.co.ke *.google.com.kh *.google.ki *.google.kg *.google.co.kr
*.google.com.kw *.google.kz *.google.la *.google.com.lb *.google.li *.google.lk
*.google.co.ls *.google.lt *.google.lu *.google.lv *.google.com.ly *.google.co.ma
*.google.md *.google.me *.google.mg *.google.mk *.google.ml *.google.com.mm
*.google.mn *.google.com.mt *.google.mu *.google.mv *.google.mw *.google.com.mx
*.google.com.my *.google.co.mz *.google.com.na *.google.com.ng *.google.com.ni
*.google.ne *.google.nl *.google.no *.google.com.np *.google.nr *.google.nu
*.google.co.nz *.google.com.om *.google.com.pa *.google.com.pe *.google.com.pg
*.google.com.ph *.google.com.pk *.google.pl *.google.pn *.google.com.pr *.google.ps
*.google.pt *.google.com.py *.google.com.qa *.google.ro *.google.ru *.google.rw
*.google.com.sa *.google.com.sb *.google.sc *.google.se *.google.com.sg *.google.sh
*.google.si *.google.sk *.google.com.sl *.google.sn *.google.so *.google.sm
*.google.sr *.google.st *.google.com.sv *.google.td *.google.tg *.google.co.th
*.google.com.tj *.google.tl *.google.tm *.google.tn *.google.to *.google.com.tr
*.google.tt *.google.com.tw *.google.co.tz *.google.com.ua *.google.co.ug
*.google.co.uk *.google.com.uy *.google.co.uz *.google.com.vc *.google.co.ve
*.google.co.vi *.google.com.vn *.google.vu *.google.ws *.google.rs *.google.co.za
*.google.co.zm *.google.co.zw *.google.cat *.cookiebot.com *.clarity.ms *.doubleclick.net
*.googlesyndication.com *.googletagmanager.com *.google.com track.hubspot.com
*.facebook.com *.facebook.net *.analytics.google.com *.google-analytics.com
*.quora.com *.linkedin.com analytics.twitter.com t.co/1/i/adsct secure.gravatar.com;
frame-src ''self'' consentcdn.cookiebot.com td.doubleclick.net www.googletagmanager.com
www.facebook.com www.recaptcha.net translate.googleapis.com; frame-ancestors
''self''; media-src ''self'' data: blob: beacon-v2.helpscout.net ssl.gstatic.com;
object-src ''self'' beacon-v2.helpscout.net; report-uri https://errors.rw.rw/api/37/security/?sentry_key=5a0cc8a2cb4f49a8b9043c602e4ec0ab'
X-Request-Id:
- 686a6f89-9c76-4c1b-89fc-54b1938cdb13
X-Runtime:
- '0.105070'
X-Cloud-Trace-Context:
- 4652627853d047ed8f5ca9c363420951;o=0
Strict-Transport-Security:
- max-age=0
Cf-Cache-Status:
- DYNAMIC
Cf-Ray:
- 99a646705c8e5779-WAW
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: UTF-8
string: '{"id":2568509,"name":"Original Project","share_links":{"admin":"https://mailtrap.io/projects/2568509/share/REDACTED","viewer":"https://mailtrap.io/projects/2568509/share/REDACTED"},"inboxes":[],"permissions":{"can_read":true,"can_update":true,"can_destroy":true,"can_leave":false}}'
recorded_at: Thu, 06 Nov 2025 17:22:49 GMT
- request:
method: patch
uri: https://mailtrap.io/api/accounts/ACCOUNT_ID/projects/2568509
body:
encoding: UTF-8
string: '{"project":{"name":"Updated Project"}}'
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- mailtrap-ruby (https://github.com/mailtrap/mailtrap-ruby)
Host:
- mailtrap.io
Authorization:
- Bearer <BEARER_TOKEN>
Content-Type:
- application/json
response:
status:
code: 200
message: OK
headers:
Date:
- Thu, 06 Nov 2025 17:22:49 GMT
Content-Type:
- application/json; charset=utf-8
Transfer-Encoding:
- chunked
Connection:
- keep-alive
Server:
- cloudflare
X-Frame-Options:
- SAMEORIGIN
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Permitted-Cross-Domain-Policies:
- none
Referrer-Policy:
- strict-origin-when-cross-origin
Vary:
- Accept
X-Mailtrap-Version:
- v2
X-Ratelimit-Limit:
- '150'
X-Ratelimit-Remaining:
- '138'
Etag:
- W/"c23f6ba7654468fa9adf039f42cec65c"
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'default-src ''self''; style-src ''self'' data: blob: ''unsafe-inline'' assets.mailtrap.io
www.googletagmanager.com fonts.googleapis.com; font-src ''self'' data: blob:
''unsafe-inline'' assets.mailtrap.io static.hsappstatic.net fonts.gstatic.cn
fonts.gstatic.com *.s-microsoft.com use.typekit.net; script-src ''self'' data:
blob: assets.mailtrap.io *.cookiebot.com www.clarity.ms *.doubleclick.net
*.googlesyndication.com *.googletagmanager.com www.googleadservices.com www.google.com
beacon-v2.helpscout.net js.hs-analytics.net js.hs-banner.com js.hs-scripts.com
cdn.firstpromoter.com connect.facebook.net www.recaptcha.net www.gstatic.cn
www.gstatic.com *.quora.com static.ads-twitter.com snap.licdn.com *.growthbook.io
translate.google.com ''nonce-js-RJj+3YeyA4sTpTsvtraJOA==''; connect-src ''self''
assets.mailtrap.io wss://mailtrap.io *.google.com *.google.ad *.google.ae
*.google.com.af *.google.com.ag *.google.al *.google.am *.google.co.ao *.google.com.ar
*.google.as *.google.at *.google.com.au *.google.az *.google.ba *.google.com.bd
*.google.be *.google.bf *.google.bg *.google.com.bh *.google.bi *.google.bj
*.google.com.bn *.google.com.bo *.google.com.br *.google.bs *.google.bt *.google.co.bw
*.google.by *.google.com.bz *.google.ca *.google.cd *.google.cf *.google.cg
*.google.ch *.google.ci *.google.co.ck *.google.cl *.google.cm *.google.cn
*.google.com.co *.google.co.cr *.google.com.cu *.google.cv *.google.com.cy
*.google.cz *.google.de *.google.dj *.google.dk *.google.dm *.google.com.do
*.google.dz *.google.com.ec *.google.ee *.google.com.eg *.google.es *.google.com.et
*.google.fi *.google.com.fj *.google.fm *.google.fr *.google.ga *.google.ge
*.google.gg *.google.com.gh *.google.com.gi *.google.gl *.google.gm *.google.gr
*.google.com.gt *.google.gy *.google.com.hk *.google.hn *.google.hr *.google.ht
*.google.hu *.google.co.id *.google.ie *.google.co.il *.google.im *.google.co.in
*.google.iq *.google.is *.google.it *.google.je *.google.com.jm *.google.jo
*.google.co.jp *.google.co.ke *.google.com.kh *.google.ki *.google.kg *.google.co.kr
*.google.com.kw *.google.kz *.google.la *.google.com.lb *.google.li *.google.lk
*.google.co.ls *.google.lt *.google.lu *.google.lv *.google.com.ly *.google.co.ma
*.google.md *.google.me *.google.mg *.google.mk *.google.ml *.google.com.mm
*.google.mn *.google.com.mt *.google.mu *.google.mv *.google.mw *.google.com.mx
*.google.com.my *.google.co.mz *.google.com.na *.google.com.ng *.google.com.ni
*.google.ne *.google.nl *.google.no *.google.com.np *.google.nr *.google.nu
*.google.co.nz *.google.com.om *.google.com.pa *.google.com.pe *.google.com.pg
*.google.com.ph *.google.com.pk *.google.pl *.google.pn *.google.com.pr *.google.ps
*.google.pt *.google.com.py *.google.com.qa *.google.ro *.google.ru *.google.rw
*.google.com.sa *.google.com.sb *.google.sc *.google.se *.google.com.sg *.google.sh
*.google.si *.google.sk *.google.com.sl *.google.sn *.google.so *.google.sm
*.google.sr *.google.st *.google.com.sv *.google.td *.google.tg *.google.co.th
*.google.com.tj *.google.tl *.google.tm *.google.tn *.google.to *.google.com.tr
*.google.tt *.google.com.tw *.google.co.tz *.google.com.ua *.google.co.ug
*.google.co.uk *.google.com.uy *.google.co.uz *.google.com.vc *.google.co.ve
*.google.co.vi *.google.com.vn *.google.vu *.google.ws *.google.rs *.google.co.za
*.google.co.zm *.google.co.zw *.google.cat errors.rw.rw *.cookiebot.com *.clarity.ms
*.g.doubleclick.net *.googlesyndication.com *.googletagmanager.com www.google.com
wss://ws-helpscout.pusher.com sockjs-helpscout.pusher.com *.helpscout.net
*.firstpromoter.com connect.facebook.net *.facebook.com www.recaptcha.net
*.analytics.google.com *.google-analytics.com *.quora.com *.linkedin.com analytics.twitter.com
t.co/1/i/adsct *.growthbook.io meta-gateway.mailtrap.io translate-pa.googleapis.com;
img-src ''self'' data: blob: assets.mailtrap.io *.google.com *.google.ad *.google.ae
*.google.com.af *.google.com.ag *.google.al *.google.am *.google.co.ao *.google.com.ar
*.google.as *.google.at *.google.com.au *.google.az *.google.ba *.google.com.bd
*.google.be *.google.bf *.google.bg *.google.com.bh *.google.bi *.google.bj
*.google.com.bn *.google.com.bo *.google.com.br *.google.bs *.google.bt *.google.co.bw
*.google.by *.google.com.bz *.google.ca *.google.cd *.google.cf *.google.cg
*.google.ch *.google.ci *.google.co.ck *.google.cl *.google.cm *.google.cn
*.google.com.co *.google.co.cr *.google.com.cu *.google.cv *.google.com.cy
*.google.cz *.google.de *.google.dj *.google.dk *.google.dm *.google.com.do
*.google.dz *.google.com.ec *.google.ee *.google.com.eg *.google.es *.google.com.et
*.google.fi *.google.com.fj *.google.fm *.google.fr *.google.ga *.google.ge
*.google.gg *.google.com.gh *.google.com.gi *.google.gl *.google.gm *.google.gr
*.google.com.gt *.google.gy *.google.com.hk *.google.hn *.google.hr *.google.ht
*.google.hu *.google.co.id *.google.ie *.google.co.il *.google.im *.google.co.in
*.google.iq *.google.is *.google.it *.google.je *.google.com.jm *.google.jo
*.google.co.jp *.google.co.ke *.google.com.kh *.google.ki *.google.kg *.google.co.kr
*.google.com.kw *.google.kz *.google.la *.google.com.lb *.google.li *.google.lk
*.google.co.ls *.google.lt *.google.lu *.google.lv *.google.com.ly *.google.co.ma
*.google.md *.google.me *.google.mg *.google.mk *.google.ml *.google.com.mm
*.google.mn *.google.com.mt *.google.mu *.google.mv *.google.mw *.google.com.mx
*.google.com.my *.google.co.mz *.google.com.na *.google.com.ng *.google.com.ni
*.google.ne *.google.nl *.google.no *.google.com.np *.google.nr *.google.nu
*.google.co.nz *.google.com.om *.google.com.pa *.google.com.pe *.google.com.pg
*.google.com.ph *.google.com.pk *.google.pl *.google.pn *.google.com.pr *.google.ps
*.google.pt *.google.com.py *.google.com.qa *.google.ro *.google.ru *.google.rw
*.google.com.sa *.google.com.sb *.google.sc *.google.se *.google.com.sg *.google.sh
*.google.si *.google.sk *.google.com.sl *.google.sn *.google.so *.google.sm
*.google.sr *.google.st *.google.com.sv *.google.td *.google.tg *.google.co.th
*.google.com.tj *.google.tl *.google.tm *.google.tn *.google.to *.google.com.tr
*.google.tt *.google.com.tw *.google.co.tz *.google.com.ua *.google.co.ug
*.google.co.uk *.google.com.uy *.google.co.uz *.google.com.vc *.google.co.ve
*.google.co.vi *.google.com.vn *.google.vu *.google.ws *.google.rs *.google.co.za
*.google.co.zm *.google.co.zw *.google.cat *.cookiebot.com *.clarity.ms *.doubleclick.net
*.googlesyndication.com *.googletagmanager.com *.google.com track.hubspot.com
*.facebook.com *.facebook.net *.analytics.google.com *.google-analytics.com
*.quora.com *.linkedin.com analytics.twitter.com t.co/1/i/adsct secure.gravatar.com;
frame-src ''self'' consentcdn.cookiebot.com td.doubleclick.net www.googletagmanager.com
www.facebook.com www.recaptcha.net translate.googleapis.com; frame-ancestors
''self''; media-src ''self'' data: blob: beacon-v2.helpscout.net ssl.gstatic.com;
object-src ''self'' beacon-v2.helpscout.net; report-uri https://errors.rw.rw/api/37/security/?sentry_key=5a0cc8a2cb4f49a8b9043c602e4ec0ab'
X-Request-Id:
- 6341454c-7091-4a97-a74d-94d931e50645
X-Runtime:
- '0.072047'
X-Cloud-Trace-Context:
- e232a4dfcd1e404ec34b5d6123563891;o=0
Strict-Transport-Security:
- max-age=0
Cf-Cache-Status:
- DYNAMIC
Cf-Ray:
- 99a646721c9deeb2-WAW
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: UTF-8
string: '{"id":2568509,"name":"Updated Project","share_links":{"admin":"https://mailtrap.io/projects/2568509/share/REDACTED","viewer":"https://mailtrap.io/projects/2568509/share/REDACTED"},"inboxes":[],"permissions":{"can_read":true,"can_update":true,"can_destroy":true,"can_leave":false}}'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

VCR cassette fixture is properly configured; duplication warrants verification.

Sensitive data is correctly redacted (Bearer tokens, ACCOUNT_ID, share_links). However, the directory structure (_update/with_hash_request/) suggests this cassette tests a different request variant than the sibling cassette. Verify in the test suite that both fixtures serve distinct purposes and aren't redundant copies.

Run the following to verify both cassettes are actively used by different test cases:


🏁 Script executed:

#!/bin/bash
# Description: Verify both VCR cassettes are used in different test contexts

# Search for cassette usage in project specs
rg -n "maps_response_data_to_Project_object" spec/

Length of output: 0



🏁 Script executed:

#!/bin/bash
# Search for ProjectsAPI test files and VCR cassette configuration

echo "=== Finding ProjectsAPI spec files ==="
fd -type f "projects.*spec" spec/

echo -e "\n=== Searching for VCR cassette configuration patterns ==="
rg -n "vcr\." spec/ --type=rb | head -20

echo -e "\n=== Searching for '_update' cassette references ==="
rg -n "_update" spec/ --type=rb | grep -i cassette

echo -e "\n=== Checking cassette directory structure ==="
fd -type f "maps_response_data_to_Project_object" .

Length of output: 593


🏁 Script executed:

#!/bin/bash
# Search for how VCR cassettes are used and if this specific cassette is referenced

echo "=== Finding ProjectsAPI spec files ==="
fd -type f "projects.*spec" spec/

echo -e "\n=== Searching for VCR cassette use patterns ==="
rg -n "vcr\." spec/ --type=rb -A2 | head -30

echo -e "\n=== Searching for any '_update' cassette or test references ==="
rg -n "with_hash_request|update.*project" spec/ --type=rb | head -20

echo -e "\n=== Verifying cassette file existence ==="
fd -type f "maps_response_data_to_Project_object"

Length of output: 591



🏁 Script executed:

#!/bin/bash
# Fix syntax errors and search for spec files and VCR usage

echo "=== Finding spec files with 'projects' in name ==="
fd -type f "projects" spec/

echo -e "\n=== Finding all spec files ==="
fd -type f -name "*spec.rb" spec/ | head -20

echo -e "\n=== Searching for VCR cassette patterns in spec files ==="
rg -n "use_cassette|vcr" spec/ -A2 | head -40

echo -e "\n=== Searching for 'update' test context ==="
rg -n "describe.*update|it.*update" spec/ | head -20

echo -e "\n=== Checking cassette fixture directory ==="
fd -type d "Mailtrap_ProjectsAPI"

Length of output: 6225



🏁 Script executed:

#!/bin/bash
# View the projects_api_spec.rb update tests and check cassette files

echo "=== View projects_api_spec.rb update tests ==="
sed -n '107,160p' spec/mailtrap/projects_api_spec.rb

echo -e "\n=== List all cassette files under _update ==="
find spec/fixtures/vcr_cassettes -type d -name "_update" -exec find {} -type f \;

echo -e "\n=== Search for any 'with_hash_request' references in spec files ==="
rg -n "with_hash_request" spec/

Length of output: 3702


Redundant test context detected; both cassettes serve identical test cases.

The cassette files are properly discoverable by RSpec's VCR auto-discovery mechanism (configured via :vcr metadata and configure_rspec_metadata!). However, verification reveals the "with_hash_request" context is functionally identical to the default test case:

  • Both use the same request format: { name: 'Updated Project' }
  • Both have identical assertions (verifying Project object with updated name)
  • The context name suggests testing an alternate request format, but doesn't

Remove the redundant "with_hash_request" context from spec/mailtrap/projects_api_spec.rb (lines 131-144) and delete the corresponding cassette file spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml.

🤖 Prompt for AI Agents
In spec/mailtrap/projects_api_spec.rb around lines 131-144 and the cassette file
spec/fixtures/vcr_cassettes/Mailtrap_ProjectsAPI/_update/with_hash_request/maps_response_data_to_Project_object.yml
(lines 1-331), the "with_hash_request" test context is redundant (identical
request/expectations to the default); remove the entire with_hash_request
context block from the spec (lines 131-144) and delete the corresponding
cassette YAML file, then run the test suite to ensure no remaining references to
that cassette.

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.

Add support of Emails Sandbox (Testing) API: Projects

4 participants