feat: add OAuth grants support#197
Conversation
Add Resend::OAuthGrants with list and remove methods, wrapping the two new OAuth endpoints: - GET /oauth/grants - DELETE /oauth/grants/:oauth_grant_id list supports the standard pagination params (limit, after, before) via PaginationHelper. Includes specs and an example.
|
Running ultrareview automatically — Adding OAuth grant list and revoke endpoints introduces authorization-sensitive logic where a subtle bug could expose or improperly revoke third-party access tokens, impacting data security.. I'll post findings when complete. |
| end | ||
|
|
||
| # https://resend.com/docs/api-reference/oauth/revoke-grant | ||
| def remove(oauth_grant_id = "") |
There was a problem hiding this comment.
I think this function should be called revoke, so that it's matching what the Node SDK has
There was a problem hiding this comment.
Good call — renamed to revoke to match the Node SDK in 547f316.
There was a problem hiding this comment.
Ultrareview completed in 3m 59s
3 issues found across 4 files
Confidence score: 4/5
- In
spec/oauth_grants_spec.rb, mocked nested OAuth response keys are symbols while production nested keys are strings, so these specs may pass against the mock but fail to catch real parsing/access regressions in production-like payloads — update the mock fixtures and assertions to use string-keyed nested hashes before merging. - In
spec/oauth_grants_spec.rb, the twoshould-style example descriptions are a low-risk style/readability issue rather than a functional blocker, but they can make test intent less consistent with project conventions — rename them to declarative phrases (for example, “builds a paginated path” / “lists oauth grants”) in this PR or as immediate follow-up.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="spec/oauth_grants_spec.rb">
<violation number="1" location="spec/oauth_grants_spec.rb:6">
P3: Custom agent: **No `should` in tests**
Use a declarative description without "should". For example, "lists oauth grants" is clearer and more direct — it describes what the test verifies rather than what the code should hypothetically do.</violation>
<violation number="2" location="spec/oauth_grants_spec.rb:12">
P2: The test mock responses use symbol keys for nested objects (via `"id":` Ruby syntax which creates `:id`), and the assertions access nested fields with `[:id]` — but in production, nested hash keys remain as strings from JSON parsing.
The SDK's `process_response` (in `request.rb`) only transforms top-level keys to symbols via `data.transform_keys!(&:to_sym)`. Nested hashes (like items in the `data` array) are **not** deep-transformed, so they retain their original string keys from the API response.
Because the mock bypasses `perform` entirely (via `allow_any_instance_of(…).to receive(:perform).and_return(resp)`), no key transformation happens at all — the mock is returned as-is with symbol keys. This means the tests pass with mocks but the assertions would fail with actual API data.
Consider using string keys (`'id' => …`) in the mock response for nested objects and accessing nested fields with string keys (`['id']`) in assertions to match the real response format.</violation>
<violation number="3" location="spec/oauth_grants_spec.rb:33">
P3: Custom agent: **No `should` in tests**
Use a declarative description without "should". "builds a paginated path" is more direct and follows the rule's guidance.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| "has_more": false, | ||
| "data": [ | ||
| { | ||
| "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e", |
There was a problem hiding this comment.
P2: The test mock responses use symbol keys for nested objects (via "id": Ruby syntax which creates :id), and the assertions access nested fields with [:id] — but in production, nested hash keys remain as strings from JSON parsing.
The SDK's process_response (in request.rb) only transforms top-level keys to symbols via data.transform_keys!(&:to_sym). Nested hashes (like items in the data array) are not deep-transformed, so they retain their original string keys from the API response.
Because the mock bypasses perform entirely (via allow_any_instance_of(…).to receive(:perform).and_return(resp)), no key transformation happens at all — the mock is returned as-is with symbol keys. This means the tests pass with mocks but the assertions would fail with actual API data.
Consider using string keys ('id' => …) in the mock response for nested objects and accessing nested fields with string keys (['id']) in assertions to match the real response format.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/oauth_grants_spec.rb, line 12:
<comment>The test mock responses use symbol keys for nested objects (via `"id":` Ruby syntax which creates `:id`), and the assertions access nested fields with `[:id]` — but in production, nested hash keys remain as strings from JSON parsing.
The SDK's `process_response` (in `request.rb`) only transforms top-level keys to symbols via `data.transform_keys!(&:to_sym)`. Nested hashes (like items in the `data` array) are **not** deep-transformed, so they retain their original string keys from the API response.
Because the mock bypasses `perform` entirely (via `allow_any_instance_of(…).to receive(:perform).and_return(resp)`), no key transformation happens at all — the mock is returned as-is with symbol keys. This means the tests pass with mocks but the assertions would fail with actual API data.
Consider using string keys (`'id' => …`) in the mock response for nested objects and accessing nested fields with string keys (`['id']`) in assertions to match the real response format.</comment>
<file context>
@@ -0,0 +1,77 @@
+ "has_more": false,
+ "data": [
+ {
+ "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",
+ "client_id": "client_123",
+ "scopes": ["emails:send"],
</file context>
| expect(grants[:has_more]).to be(false) | ||
| end | ||
|
|
||
| it "should build a paginated path" do |
There was a problem hiding this comment.
P3: Custom agent: No should in tests
Use a declarative description without "should". "builds a paginated path" is more direct and follows the rule's guidance.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/oauth_grants_spec.rb, line 33:
<comment>Use a declarative description without "should". "builds a paginated path" is more direct and follows the rule's guidance.</comment>
<file context>
@@ -0,0 +1,77 @@
+ expect(grants[:has_more]).to be(false)
+ end
+
+ it "should build a paginated path" do
+ expect(Resend::PaginationHelper).to receive(:build_paginated_path)
+ .with("oauth/grants", { limit: 10 })
</file context>
| RSpec.describe "OAuth Grants" do | ||
| shared_examples "oauth grants api" do | ||
| describe "list" do | ||
| it "should list oauth grants" do |
There was a problem hiding this comment.
P3: Custom agent: No should in tests
Use a declarative description without "should". For example, "lists oauth grants" is clearer and more direct — it describes what the test verifies rather than what the code should hypothetically do.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/oauth_grants_spec.rb, line 6:
<comment>Use a declarative description without "should". For example, "lists oauth grants" is clearer and more direct — it describes what the test verifies rather than what the code should hypothetically do.</comment>
<file context>
@@ -0,0 +1,77 @@
+RSpec.describe "OAuth Grants" do
+ shared_examples "oauth grants api" do
+ describe "list" do
+ it "should list oauth grants" do
+ resp = {
+ "object": "list",
</file context>
Match the Node SDK's method name (per review feedback).
There was a problem hiding this comment.
0 issues found across 3 files (changes from recent commits).
Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.
Re-trigger cubic
Adds support for the two newly-launched OAuth endpoints:
GET /oauth/grantsDELETE /oauth/grants/:oauthGrantIdChanges
Resend::OAuthGrantsmodule with:list(params = {})— lists OAuth grants. Supports standard pagination params (limit,after,before) viaPaginationHelper.remove(oauth_grant_id)— revokes an OAuth grant.lib/resend.rb.examples/oauth_grants.rb.Usage
Testing
bundle exec rspec→ 306 examples, 0 failuresbundle exec rubocop→ no offensesReferences
Summary by cubic
Add OAuth grants support to let users list and revoke OAuth grants through simple Ruby methods. This wraps the new API endpoints and adds pagination and examples.
Resend::OAuthGrantswithlist(params = {})andrevoke(oauth_grant_id), wrappingGET /oauth/grantsandDELETE /oauth/grants/:oauth_grant_id.limit,after,before) viaResend::PaginationHelper.examples/oauth_grants.rb.Written for commit 547f316. Summary will update on new commits.