From 0b8193efa76e5173ce2e2ccda43fdf9ccdeb08fa Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Mon, 3 Aug 2026 14:26:37 +0200 Subject: [PATCH 1/5] MT-23076: add expires_at to api token create options --- lib/mailtrap/api_tokens_api.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mailtrap/api_tokens_api.rb b/lib/mailtrap/api_tokens_api.rb index 5249bb84..9063ae39 100644 --- a/lib/mailtrap/api_tokens_api.rb +++ b/lib/mailtrap/api_tokens_api.rb @@ -7,7 +7,7 @@ module Mailtrap class ApiTokensAPI include BaseAPI - self.supported_options = %i[name resources].freeze + self.supported_options = %i[name expires_at resources].freeze self.response_class = ApiToken @@ -30,6 +30,9 @@ def get(token_id) # Creates a new API token. The full `token` value is returned ONLY ONCE — store it securely. # @param [Hash] options The parameters to create # @option options [String] :name Display name for the token + # @option options [String, nil] :expires_at Optional token expiration as an ISO 8601 date-time. + # Omit for the server default (a 1-year default is being rolled out). Pass explicit nil for + # a token that never expires. Past or more-than-5-years-ahead values are rejected with 422 # @option options [Array] :resources Permissions to assign # - `{ resource_type:, resource_id:, access_level: }` # @return [ApiToken] Created token (full `token` value populated) From f06bd9c828ad2afe7fa28f111bcf7f56cfc68609 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Mon, 3 Aug 2026 14:27:02 +0200 Subject: [PATCH 2/5] MT-23076: accept expires_at options on api token reset --- lib/mailtrap/api_tokens_api.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/mailtrap/api_tokens_api.rb b/lib/mailtrap/api_tokens_api.rb index 9063ae39..fdf46796 100644 --- a/lib/mailtrap/api_tokens_api.rb +++ b/lib/mailtrap/api_tokens_api.rb @@ -46,10 +46,17 @@ def create(options) # The old token stops working after a short grace period. The new `token` value is # returned ONLY ONCE — store it securely # @param token_id [Integer] The API token ID + # @param [Hash] options The reset parameters + # @option options [String, nil] :expires_at Optional token expiration as an ISO 8601 date-time. + # Omit for the server default (a 1-year default is being rolled out). Pass explicit nil for + # a token that never expires. Past or more-than-5-years-ahead values are rejected with 422 # @return [ApiToken] New token (full `token` value populated) # @!macro api_errors - def reset(token_id) - response = client.post("#{base_path}/#{token_id}/reset") + # @raise [ArgumentError] If invalid options are provided + def reset(token_id, options = {}) + validate_options!(options, %i[expires_at]) + # An empty hash must not be sent as a body — the endpoint historically takes no body + response = client.post("#{base_path}/#{token_id}/reset", options.empty? ? nil : options) handle_response(response) end From 44601f1fc1cae6864209be63b104b690c4daca72 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Mon, 3 Aug 2026 14:32:17 +0200 Subject: [PATCH 3/5] MT-23076: cover api token expires_at request serialization --- ...ializes_expires_at_in_the_request_body.yml | 74 +++++++++++++++ .../raises_a_Mailtrap_Error.yml | 71 ++++++++++++++ .../serializes_expires_at_as_JSON_null.yml | 74 +++++++++++++++ ...ializes_expires_at_in_the_request_body.yml | 75 +++++++++++++++ .../raises_a_Mailtrap_Error.yml | 71 ++++++++++++++ .../serializes_expires_at_as_JSON_null.yml | 75 +++++++++++++++ spec/mailtrap/api_tokens_api_spec.rb | 95 +++++++++++++++++++ 7 files changed, 535 insertions(+) create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml new file mode 100644 index 00000000..cd8c69e6 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/api_tokens + body: + encoding: UTF-8 + string: '{"name":"Ruby SDK Test Token","expires_at":"2027-06-01T00:00:00Z","resources":[{"resource_type":"account","resource_id":1111111,"access_level":100}]}' + 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 + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 03 Aug 2026 10:00:00 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: + - '149' + Etag: + - W/"1f0d2c3b4a5968778695a4b3c2d1e0f9" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.141212' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"resources":[{"resource_type":"account","resource_id":1719941,"access_level":100}],"id":2498720,"name":"Ruby + SDK Test Token","last_4_digits":"9d10","created_by":"SDK Dev Account Token","expires_at":"2027-06-01T00:00:00Z","token":"7f2f0a9b8f1e4bfa9d3c5e6a7b8c9d10"}' + recorded_at: Mon, 03 Aug 2026 10:00:00 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml new file mode 100644 index 00000000..c3efbc6f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/api_tokens + body: + encoding: UTF-8 + string: '{"name":"Ruby SDK Test Token","expires_at":"2020-01-01T00:00:00Z","resources":[{"resource_type":"account","resource_id":1111111,"access_level":100}]}' + 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 + Content-Type: + - application/json + response: + status: + code: 422 + message: Unprocessable Entity + headers: + Date: + - Mon, 03 Aug 2026 10:00:02 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '61' + 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: + - '147' + Cache-Control: + - no-cache + X-Runtime: + - '0.011321' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"errors":{"base":["Expiration date must be in the future"]}}' + recorded_at: Mon, 03 Aug 2026 10:00:02 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml new file mode 100644 index 00000000..6f4c6caa --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_create/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/api_tokens + body: + encoding: UTF-8 + string: '{"name":"Ruby SDK Test Token","expires_at":null,"resources":[{"resource_type":"account","resource_id":1111111,"access_level":100}]}' + 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 + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 03 Aug 2026 10:00:01 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: + - '148' + Etag: + - W/"2e1d0c3b4a5968778695a4b3c2d1e0a8" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.132404' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"resources":[{"resource_type":"account","resource_id":1719941,"access_level":100}],"id":2498721,"name":"Ruby + SDK Test Token","last_4_digits":"4d21","created_by":"SDK Dev Account Token","expires_at":null,"token":"4b1c2d3e4f5a6b7c8d9e0f1a2b3c4d21"}' + recorded_at: Mon, 03 Aug 2026 10:00:01 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml new file mode 100644 index 00000000..90980e36 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_given/serializes_expires_at_in_the_request_body.yml @@ -0,0 +1,75 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/api_tokens/2498713/reset + body: + encoding: UTF-8 + string: '{"expires_at":"2027-06-01T00:00:00Z"}' + 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 + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 03 Aug 2026 10:00:03 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: + - '149' + Etag: + - W/"3f2e1d0c4b5a69788796a5b4c3d2e1b7" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.157893' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"resources":[{"resource_type":"account","resource_id":1719941,"access_level":100}],"id":2498722,"name":"Ruby + SDK Test Token 2498722","last_4_digits":"899a","created_by":"SDK Dev Account + Token","expires_at":"2027-06-01T00:00:00Z","token":"a3b4c5d6e7f80912233445566778899a"}' + recorded_at: Mon, 03 Aug 2026 10:00:03 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml new file mode 100644 index 00000000..ef2975ae --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_in_the_past/raises_a_Mailtrap_Error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/api_tokens/2498713/reset + body: + encoding: UTF-8 + string: '{"expires_at":"2020-01-01T00:00:00Z"}' + 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 + Content-Type: + - application/json + response: + status: + code: 422 + message: Unprocessable Entity + headers: + Date: + - Mon, 03 Aug 2026 10:00:05 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '61' + 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: + - '146' + Cache-Control: + - no-cache + X-Runtime: + - '0.012108' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"errors":{"base":["Expiration date must be in the future"]}}' + recorded_at: Mon, 03 Aug 2026 10:00:05 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml new file mode 100644 index 00000000..46a94239 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_reset/when_expires_at_is_nil/serializes_expires_at_as_JSON_null.yml @@ -0,0 +1,75 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/api_tokens/2498713/reset + body: + encoding: UTF-8 + string: '{"expires_at":null}' + 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 + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 03 Aug 2026 10:00:04 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: + - '148' + Etag: + - W/"4a3f2e1d5c6b7a899807b6c5d4e3f2c6" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.149015' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"resources":[{"resource_type":"account","resource_id":1719941,"access_level":100}],"id":2498723,"name":"Ruby + SDK Test Token 2498723","last_4_digits":"6789","created_by":"SDK Dev Account + Token","expires_at":null,"token":"0918273645abcdefabcdef0123456789"}' + recorded_at: Mon, 03 Aug 2026 10:00:04 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/api_tokens_api_spec.rb b/spec/mailtrap/api_tokens_api_spec.rb index 3b494c20..4e0886a4 100644 --- a/spec/mailtrap/api_tokens_api_spec.rb +++ b/spec/mailtrap/api_tokens_api_spec.rb @@ -49,6 +49,7 @@ describe '#create' do subject(:create) { api_tokens_api.create(request) } + let(:url) { "https://mailtrap.io/api/accounts/#{account_id}/api_tokens" } let(:request) do { name: 'Ruby SDK Test Token', @@ -65,6 +66,61 @@ name: 'Ruby SDK Test Token', token: an_instance_of(String) ) + expect(WebMock).to(have_requested(:post, url).with { |req| !req.body.include?('expires_at') }) + end + + context 'when expires_at is given' do + let(:request) do + { + name: 'Ruby SDK Test Token', + expires_at: '2027-06-01T00:00:00Z', + resources: [ + { resource_type: 'account', resource_id: account_id, access_level: 100 } + ] + } + end + + it 'serializes expires_at in the request body' do + expect(create).to have_attributes( + expires_at: '2027-06-01T00:00:00Z', + token: an_instance_of(String) + ) + expect(WebMock).to(have_requested(:post, url) + .with { |req| req.body.include?('"expires_at":"2027-06-01T00:00:00Z"') }) + end + end + + context 'when expires_at is nil' do + let(:request) do + { + name: 'Ruby SDK Test Token', + expires_at: nil, + resources: [ + { resource_type: 'account', resource_id: account_id, access_level: 100 } + ] + } + end + + it 'serializes expires_at as JSON null' do + expect(create).to have_attributes(expires_at: nil, token: an_instance_of(String)) + expect(WebMock).to(have_requested(:post, url).with { |req| req.body.include?('"expires_at":null') }) + end + end + + context 'when expires_at is in the past' do + let(:request) do + { + name: 'Ruby SDK Test Token', + expires_at: '2020-01-01T00:00:00Z', + resources: [ + { resource_type: 'account', resource_id: account_id, access_level: 100 } + ] + } + end + + it 'raises a Mailtrap::Error' do + expect { create }.to raise_error(Mailtrap::Error) + end end context 'when invalid options are provided' do @@ -88,6 +144,7 @@ subject(:reset) { api_tokens_api.reset(token_id) } let(:token_id) { 2_498_713 } + let(:url) { "https://mailtrap.io/api/accounts/#{account_id}/api_tokens/#{token_id}/reset" } it 'maps response data to ApiToken with new token value' do expect(reset).to be_a(Mailtrap::ApiToken) @@ -95,6 +152,44 @@ id: an_instance_of(Integer), token: an_instance_of(String) ) + expect(WebMock).to(have_requested(:post, url).with { |req| req.body.to_s.empty? }) + end + + context 'when expires_at is given' do + subject(:reset) { api_tokens_api.reset(token_id, expires_at: '2027-06-01T00:00:00Z') } + + it 'serializes expires_at in the request body' do + expect(reset).to have_attributes( + expires_at: '2027-06-01T00:00:00Z', + token: an_instance_of(String) + ) + expect(WebMock).to have_requested(:post, url).with(body: '{"expires_at":"2027-06-01T00:00:00Z"}') + end + end + + context 'when expires_at is nil' do + subject(:reset) { api_tokens_api.reset(token_id, expires_at: nil) } + + it 'serializes expires_at as JSON null' do + expect(reset).to have_attributes(expires_at: nil, token: an_instance_of(String)) + expect(WebMock).to have_requested(:post, url).with(body: '{"expires_at":null}') + end + end + + context 'when expires_at is in the past' do + subject(:reset) { api_tokens_api.reset(token_id, expires_at: '2020-01-01T00:00:00Z') } + + it 'raises a Mailtrap::Error' do + expect { reset }.to raise_error(Mailtrap::Error) + end + end + + context 'when invalid options are provided' do + subject(:reset) { api_tokens_api.reset(token_id, unknown_option: true) } + + it 'raises ArgumentError' do + expect { reset }.to raise_error(ArgumentError, /invalid options are given/) + end end context 'when token does not exist' do From c21cb786c8d799ee1dcc1fb12b5254fdb024bf7f Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Mon, 3 Aug 2026 14:32:48 +0200 Subject: [PATCH 4/5] MT-23076: show expires_at in api tokens example --- examples/api_tokens_api.rb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/api_tokens_api.rb b/examples/api_tokens_api.rb index 8954fddc..0d0933ca 100644 --- a/examples/api_tokens_api.rb +++ b/examples/api_tokens_api.rb @@ -13,18 +13,37 @@ # => # # Create a new API token. The full `token` value is returned ONLY once — store it securely. +# `expires_at` is optional – omit it for the server default (a 1-year default is being +# rolled out), pass an ISO 8601 date-time for an explicit expiry, or pass explicit nil +# for a token that never expires. api_tokens.create( name: 'My API Token', + expires_at: '2027-06-01T00:00:00Z', resources: [ { resource_type: 'account', resource_id: account_id, access_level: 100 } ] ) -# => # +# => # + +# Create a token that never expires +api_tokens.create( + name: 'My API Token', + expires_at: nil, + resources: [ + { resource_type: 'account', resource_id: account_id, access_level: 100 } + ] +) +# => # # Reset a token — expires the old value (short grace period) and returns a new value once. +# `expires_at` is optional and works the same as on create. api_tokens.reset(12_345) # => # +# Reset a token with an explicit expiry for the new token +api_tokens.reset(12_345, expires_at: '2027-06-01T00:00:00Z') +# => # + # Permanently delete a token api_tokens.delete(12_345) # => nil From 3856477d8c6757bfc97d2bb84cc534a9313340d5 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Mon, 3 Aug 2026 14:33:56 +0200 Subject: [PATCH 5/5] MT-23076: link api tokens and permissions examples in readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1b669971..9ba4abc0 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,9 @@ General: - Accounts API – [`accounts_api.rb`](examples/accounts_api.rb) - Account Accesses API – [`account_accesses_api.rb`](examples/account_accesses_api.rb) +- API Tokens API – [`api_tokens_api.rb`](examples/api_tokens_api.rb) - Billing API – [`billing_api.rb`](examples/billing_api.rb) +- Permissions API – [`permissions_api.rb`](examples/permissions_api.rb) - Templates API – [`email_templates_api.rb`](examples/email_templates_api.rb) - Action Mailer – [`action_mailer.rb`](examples/action_mailer.rb) - Verifying webhook signatures – [`webhooks_signature_verification.ru`](examples/webhooks_signature_verification.ru)