Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### Unreleased
* Fixed HTTParty content type issue when request body is nil - POST, PUT, PATCH, and DELETE now default to empty object to ensure Content-Type: application/json is sent (#536)
* Added support for request_body parameter on DELETE (e.g. cancellation_reason for bookings) (#536)

### [6.7.1]
* Fix large attachment handling with string keys and custom content_ids

Expand Down
15 changes: 10 additions & 5 deletions lib/nylas/handler/api_operations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@ module Post
# @param path [String] Destination path for the call.
# @param query_params [Hash, {}] Query params to pass to the call.
# @param request_body [Hash, nil] Request body to pass to the call.
# Defaults to {} when nil to ensure Content-Type: application/json is sent.
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
# @return [Array(Hash, String, Hash)] Nylas data object, API Request ID, and response headers.
def post(path:, query_params: {}, request_body: nil, headers: {})
response = execute(
method: :post,
path: path,
query: query_params,
payload: request_body,
payload: request_body || {},
headers: headers,
api_key: api_key,
timeout: timeout
Expand All @@ -90,14 +91,15 @@ module Put
# @param path [String] Destination path for the call.
# @param query_params [Hash, {}] Query params to pass to the call.
# @param request_body [Hash, nil] Request body to pass to the call.
# Defaults to {} when nil to ensure Content-Type: application/json is sent.
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
# @return Nylas data object and API Request ID.
def put(path:, query_params: {}, request_body: nil, headers: {})
response = execute(
method: :put,
path: path,
query: query_params,
payload: request_body,
payload: request_body || {},
headers: headers,
api_key: api_key,
timeout: timeout
Expand All @@ -117,14 +119,15 @@ module Patch
# @param path [String] Destination path for the call.
# @param query_params [Hash, {}] Query params to pass to the call.
# @param request_body [Hash, nil] Request body to pass to the call.
# Defaults to {} when nil to ensure Content-Type: application/json is sent.
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
# @return Nylas data object and API Request ID.
def patch(path:, query_params: {}, request_body: nil, headers: {})
response = execute(
method: :patch,
path: path,
query: query_params,
payload: request_body,
payload: request_body || {},
headers: headers,
api_key: api_key,
timeout: timeout
Expand All @@ -143,15 +146,17 @@ module Delete
#
# @param path [String] Destination path for the call.
# @param query_params [Hash, {}] Query params to pass to the call.
# @param request_body [Hash, nil] Optional request body (e.g. cancellation_reason for bookings).
# Defaults to {} to ensure Content-Type: application/json is sent.
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
# @return Nylas data object and API Request ID.
def delete(path:, query_params: {}, headers: {})
def delete(path:, query_params: {}, request_body: nil, headers: {})
response = execute(
method: :delete,
path: path,
query: query_params,
headers: headers,
payload: nil,
payload: request_body || {},
api_key: api_key,
timeout: timeout
)
Expand Down
6 changes: 4 additions & 2 deletions lib/nylas/resources/bookings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ def confirm(booking_id:, request_body:, query_params: nil)
# Delete a booking.
# @param booking_id [String] The id of the booking to delete.
# @param query_params [Hash, nil] Query params to pass to the request.
# @param request_body [Hash, nil] Optional body params (e.g. cancellation_reason).
# @return [Array(TrueClass, String)] True and the API Request ID for the delete operation.
def destroy(booking_id:, query_params: nil)
def destroy(booking_id:, query_params: nil, request_body: nil)
_, request_id = delete(
path: "#{api_uri}/v3/scheduling/bookings/#{booking_id}",
query_params: query_params
query_params: query_params,
request_body: request_body
)

[true, request_id]
Expand Down
76 changes: 70 additions & 6 deletions spec/nylas/handler/api_operations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def initialize(api_key, api_uri, timeout)
method: :post,
path: path,
query: {},
payload: nil,
payload: {},
headers: {},
api_key: api_key,
timeout: timeout
Expand All @@ -164,7 +164,7 @@ def initialize(api_key, api_uri, timeout)
method: :post,
path: path,
query: {},
payload: nil,
payload: {},
headers: {},
api_key: api_key,
timeout: timeout
Expand All @@ -174,6 +174,22 @@ def initialize(api_key, api_uri, timeout)

expect(response).to eq([mock_response[:data], mock_response[:request_id], nil])
end

it "passes request_body to execute when provided" do
path = "#{api_uri}/path"
request_body = { foo: "bar" }
allow(api_operations).to receive(:execute).with(
method: :post,
path: path,
query: {},
payload: request_body,
headers: {},
api_key: api_key,
timeout: timeout
).and_return(mock_response)

api_operations.send(:post, path: path, request_body: request_body)
end
end
end

Expand Down Expand Up @@ -206,7 +222,7 @@ def initialize(api_key, api_uri, timeout)
method: :put,
path: path,
query: {},
payload: nil,
payload: {},
headers: {},
api_key: api_key,
timeout: timeout
Expand All @@ -216,6 +232,22 @@ def initialize(api_key, api_uri, timeout)

expect(response).to eq([mock_response[:data], mock_response[:request_id]])
end

it "passes request_body to execute when provided" do
path = "#{api_uri}/path"
request_body = { foo: "bar" }
allow(api_operations).to receive(:execute).with(
method: :put,
path: path,
query: {},
payload: request_body,
headers: {},
api_key: api_key,
timeout: timeout
).and_return(mock_response)

api_operations.send(:put, path: path, request_body: request_body)
end
end
end

Expand Down Expand Up @@ -248,7 +280,7 @@ def initialize(api_key, api_uri, timeout)
method: :patch,
path: path,
query: {},
payload: nil,
payload: {},
headers: {},
api_key: api_key,
timeout: timeout
Expand All @@ -258,6 +290,22 @@ def initialize(api_key, api_uri, timeout)

expect(response).to eq([mock_response[:data], mock_response[:request_id]])
end

it "passes request_body to execute when provided" do
path = "#{api_uri}/path"
request_body = { foo: "bar" }
allow(api_operations).to receive(:execute).with(
method: :patch,
path: path,
query: {},
payload: request_body,
headers: {},
api_key: api_key,
timeout: timeout
).and_return(mock_response)

api_operations.send(:patch, path: path, request_body: request_body)
end
end
end

Expand All @@ -271,7 +319,7 @@ def initialize(api_key, api_uri, timeout)
method: :delete,
path: path,
query: query_params,
payload: nil,
payload: {},
headers: headers,
api_key: api_key,
timeout: timeout
Expand All @@ -288,7 +336,7 @@ def initialize(api_key, api_uri, timeout)
method: :delete,
path: path,
query: {},
payload: nil,
payload: {},
headers: {},
api_key: api_key,
timeout: timeout
Expand All @@ -298,6 +346,22 @@ def initialize(api_key, api_uri, timeout)

expect(response).to eq([mock_response[:data], mock_response[:request_id]])
end

it "passes request_body to execute when provided" do
path = "#{api_uri}/path"
request_body = { cancellation_reason: "Meeting cancelled" }
allow(api_operations).to receive(:execute).with(
method: :delete,
path: path,
query: {},
payload: request_body,
headers: {},
api_key: api_key,
timeout: timeout
).and_return(mock_response)

api_operations.send(:delete, path: path, request_body: request_body)
end
end
end
end
40 changes: 40 additions & 0 deletions spec/nylas/handler/http_client_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,46 @@ class TestHttpClientIntegration
end
end

describe "Integration Tests - Content-Type for body-less requests (HTTParty fix)" do
it "sends Content-Type: application/json for POST with empty payload" do
stub_request(:post, "https://test.api.nylas.com/v3/connect/revoke")
.with(
body: "{}",
headers: { "Content-Type" => "application/json" }
)
.to_return(status: 200, body: "{}", headers: { "Content-Type" => "application/json" })

http_client.send(:execute,
method: :post,
path: "https://test.api.nylas.com/v3/connect/revoke",
timeout: 30,
payload: {},
api_key: "fake-key")

expect(WebMock).to have_requested(:post, "https://test.api.nylas.com/v3/connect/revoke")
.with(headers: { "Content-Type" => "application/json" }, body: "{}")
end

it "sends Content-Type: application/json for DELETE with empty payload" do
stub_request(:delete, "https://test.api.nylas.com/v3/scheduling/bookings/booking-123")
.with(
body: "{}",
headers: { "Content-Type" => "application/json" }
)
.to_return(status: 200, body: "{}", headers: { "Content-Type" => "application/json" })

http_client.send(:execute,
method: :delete,
path: "https://test.api.nylas.com/v3/scheduling/bookings/booking-123",
timeout: 30,
payload: {},
api_key: "fake-key")

expect(WebMock).to have_requested(:delete, "https://test.api.nylas.com/v3/scheduling/bookings/booking-123")
.with(headers: { "Content-Type" => "application/json" }, body: "{}")
end
end

describe "Integration Tests - backwards compatibility" do
it "maintains the same response format as rest-client" do
response_json = { "data" => { "id" => "123", "name" => "test" } }
Expand Down
11 changes: 11 additions & 0 deletions spec/nylas/handler/http_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ class TestHttpClient
end

context "when building request with a payload" do
it "returns the correct request with empty json payload and sets Content-Type" do
payload = {}
request = http_client.send(:build_request, method: :post, path: "https://test.api.nylas.com/foo",
payload: payload, api_key: "fake-key")

expect(request[:method]).to eq(:post)
expect(request[:url]).to eq("https://test.api.nylas.com/foo")
expect(request[:payload]).to eq("{}")
expect(request[:headers]).to include("Content-type" => "application/json")
end

it "returns the correct request with a json payload" do
payload = { foo: "bar" }
request = http_client.send(:build_request, method: :post, path: "https://test.api.nylas.com/foo",
Expand Down
19 changes: 17 additions & 2 deletions spec/nylas/resources/bookings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
booking_id = "booking-123"
path = "#{api_uri}/v3/scheduling/bookings/#{booking_id}"
allow(bookings).to receive(:delete)
.with(path: path, query_params: nil)
.with(path: path, query_params: nil, request_body: nil)
.and_return(delete_response)

bookings_response = bookings.destroy(booking_id: booking_id)
Expand All @@ -197,11 +197,26 @@
query_params = { "foo": "bar" }
path = "#{api_uri}/v3/scheduling/bookings/#{booking_id}"
allow(bookings).to receive(:delete)
.with(path: path, query_params: query_params)
.with(path: path, query_params: query_params, request_body: nil)
.and_return(delete_response)

bookings_response = bookings.destroy(booking_id: booking_id, query_params: query_params)
expect(bookings_response).to eq(delete_response)
end

it "calls the delete method with request_body for cancellation_reason" do
booking_id = "booking-123"
request_body = { cancellation_reason: "Meeting no longer needed" }
path = "#{api_uri}/v3/scheduling/bookings/#{booking_id}"
allow(bookings).to receive(:delete)
.with(path: path, query_params: nil, request_body: request_body)
.and_return(delete_response)

bookings_response = bookings.destroy(
booking_id: booking_id,
request_body: request_body
)
expect(bookings_response).to eq(delete_response)
end
end
end
Loading