• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stripe / stripe-ruby / #5867

18 Apr 2024 09:24PM UTC coverage: 92.724% (-4.8%) from 97.485%
#5867

push

github

ramya-stripe
Bump version to 11.2.0

10067 of 10857 relevant lines covered (92.72%)

258.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.9
/test/stripe/api_resource_test.rb
1
# frozen_string_literal: true
2

3
require File.expand_path("../test_helper", __dir__)
1✔
4

5
module Stripe
1✔
6
  class ApiResourceTest < Test::Unit::TestCase
1✔
7
    class CustomMethodAPIResource < APIResource
1✔
8
      OBJECT_NAME = "custom_method"
1✔
9
      def self.object_name
1✔
10
        "custom_method"
1✔
11
      end
12
      custom_method :my_method, http_verb: :post
1✔
13
    end
14

15
    class NestedTestAPIResource < APIResource
1✔
16
      save_nested_resource :external_account
1✔
17
    end
18

19
    context ".custom_method" do
1✔
20
      should "call to an RPC-style method" do
1✔
21
        stub_request(:post, "#{Stripe.api_base}/v1/custom_methods/ch_123/my_method")
1✔
22
          .to_return(body: JSON.generate({}))
×
23
        CustomMethodAPIResource.my_method("ch_123")
1✔
24
      end
25

26
      should "raise an error if a non-ID is passed" do
1✔
27
        e = assert_raises ArgumentError do
1✔
28
          CustomMethodAPIResource.my_method(id: "ch_123")
1✔
29
        end
30
        assert_equal "id should be a string representing the ID of an API resource",
1✔
31
                     e.message
×
32
      end
33

34
      should "raise an error if an unsupported target is passed" do
1✔
35
        e = assert_raises ArgumentError do
1✔
36
          Stripe::Util.custom_method(CustomMethodAPIResource, String, :my_method, :post, "/")
1✔
37
        end
38
        assert_equal "Invalid target value: String. Target class should have a `resource_url` method.",
1✔
39
                     e.message
×
40
      end
41
    end
42

43
    context ".save_nested_resource" do
1✔
44
      should "can have a scalar set" do
1✔
45
        r = NestedTestAPIResource.new("test_resource")
1✔
46
        r.external_account = "tok_123"
1✔
47
        assert_equal "tok_123", r.external_account
1✔
48
      end
49

50
      should "set a flag if given an object source" do
1✔
51
        r = NestedTestAPIResource.new("test_resource")
1✔
52
        r.external_account = {
1✔
53
          object: "card",
54
        }
55
        assert_equal true, r.external_account.save_with_parent
1✔
56
      end
57
    end
58

59
    should "creating a new APIResource should not fetch over the network" do
1✔
60
      Stripe::Customer.new("someid")
1✔
61
      assert_not_requested :get, %r{#{Stripe.api_base}/.*}
1✔
62
    end
63

64
    should "creating a new APIResource from a hash should not fetch over the network" do
1✔
65
      Stripe::Customer.construct_from(id: "somecustomer",
1✔
66
                                      card: { id: "somecard", object: "card" },
67
                                      object: "customer")
68
      assert_not_requested :get, %r{#{Stripe.api_base}/.*}
1✔
69
    end
70

71
    should "setting an attribute should not cause a network request" do
1✔
72
      c = Stripe::Customer.new("cus_123")
1✔
73
      c.card = { id: "somecard", object: "card" }
1✔
74
      assert_not_requested :get, %r{#{Stripe.api_base}/.*}
1✔
75
      assert_not_requested :post, %r{#{Stripe.api_base}/.*}
1✔
76
    end
77

78
    should "accessing id should not issue a fetch" do
1✔
79
      c = Stripe::Customer.new("cus_123")
1✔
80
      c.id
1✔
81
      assert_not_requested :get, %r{#{Stripe.api_base}/.*}
1✔
82
    end
83

84
    should "not specifying api credentials should raise an exception" do
1✔
85
      Stripe.api_key = nil
1✔
86
      assert_raises Stripe::AuthenticationError do
1✔
87
        Stripe::Customer.new("cus_123").refresh
1✔
88
      end
89
    end
90

91
    should "using a nil api key should raise an exception" do
1✔
92
      assert_raises TypeError do
1✔
93
        Stripe::Customer.list({}, nil)
1✔
94
      end
95
      assert_raises TypeError do
1✔
96
        Stripe::Customer.list({}, api_key: nil)
1✔
97
      end
98
    end
99

100
    should "specifying api credentials containing whitespace should raise an exception" do
1✔
101
      Stripe.api_key = "key "
1✔
102
      assert_raises Stripe::AuthenticationError do
1✔
103
        Stripe::Customer.new("cus_123").refresh
1✔
104
      end
105
    end
106

107
    should "send expand on fetch properly" do
1✔
108
      stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
1✔
109
        .with(query: { "expand" => ["customer"] })
×
110
        .to_return(body: JSON.generate(charge_fixture))
×
111

112
      Stripe::Charge.retrieve(id: "ch_123", expand: [:customer])
1✔
113
    end
114

115
    should "preserve expand across refreshes" do
1✔
116
      stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
1✔
117
        .with(query: { "expand" => ["customer"] })
×
118
        .to_return(body: JSON.generate(charge_fixture))
×
119

120
      ch = Stripe::Charge.retrieve(id: "ch_123", expand: [:customer])
1✔
121
      ch.refresh
1✔
122
    end
123

124
    should "send expand when fetching through ListObject" do
1✔
125
      stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
1✔
126
        .to_return(body: JSON.generate(charge_fixture))
×
127

128
      stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123/refunds/re_123")
1✔
129
        .with(query: { "expand" => ["balance_transaction"] })
×
130
        .to_return(body: JSON.generate(charge_fixture))
×
131

132
      charge = Stripe::Charge.retrieve("ch_123")
1✔
133
      charge.refunds.retrieve(id: "re_123", expand: [:balance_transaction])
1✔
134
    end
135

136
    context "when specifying per-object credentials" do
1✔
137
      context "with no global API key set" do
1✔
138
        should "use the per-object credential when creating" do
1✔
139
          stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
140
            .with(headers: { "Authorization" => "Bearer sk_test_local" })
×
141
            .to_return(body: JSON.generate(charge_fixture))
×
142

143
          Stripe::Charge.create({ source: "tok_visa" },
1✔
144
                                "sk_test_local")
145
        end
146
      end
147

148
      context "with a global API key set" do
1✔
149
        setup do
1✔
150
          Stripe.api_key = "global"
2✔
151
        end
152

153
        teardown do
1✔
154
          Stripe.api_key = nil
2✔
155
        end
156

157
        should "use the per-object credential when creating" do
1✔
158
          stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
159
            .with(headers: { "Authorization" => "Bearer sk_test_local" })
×
160
            .to_return(body: JSON.generate(charge_fixture))
×
161

162
          Stripe::Charge.create({ source: "tok_visa" },
1✔
163
                                "sk_test_local")
164
        end
165

166
        should "use the per-object credential when retrieving and making other calls" do
1✔
167
          stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
1✔
168
            .with(headers: { "Authorization" => "Bearer sk_test_local" })
×
169
            .to_return(body: JSON.generate(charge_fixture))
×
170
          stub_request(:post, "#{Stripe.api_base}/v1/charges/ch_123/refunds")
1✔
171
            .with(headers: { "Authorization" => "Bearer sk_test_local" })
×
172
            .to_return(body: "{}")
×
173

174
          ch = Stripe::Charge.retrieve("ch_123", "sk_test_local")
1✔
175
          ch.refunds.create
1✔
176
        end
177
      end
178
    end
179

180
    context "with valid credentials" do
1✔
181
      should "urlencode values in GET params" do
1✔
182
        stub_request(:get, "#{Stripe.api_base}/v1/charges")
1✔
183
          .with(query: { customer: "test customer" })
×
184
          .to_return(body: JSON.generate(data: [charge_fixture]))
×
185
        charges = Stripe::Charge.list(customer: "test customer").data
1✔
186
        assert charges.is_a? Array
1✔
187
      end
188

189
      should "construct URL properly with base query parameters" do
1✔
190
        stub_request(:get, "#{Stripe.api_base}/v1/charges")
1✔
191
          .with(query: { customer: "cus_123" })
×
192
          .to_return(body: JSON.generate(data: [charge_fixture],
×
193
                                         url: "/v1/charges",
194
                                         object: "list"))
195
        charges = Stripe::Charge.list(customer: "cus_123")
1✔
196

197
        stub_request(:get, "#{Stripe.api_base}/v1/charges")
1✔
198
          .with(query: { customer: "cus_123", created: "123" })
×
199
          .to_return(body: JSON.generate(data: [charge_fixture],
×
200
                                         url: "/v1/charges",
201
                                         object: "list"))
202
        charges.list(created: 123)
1✔
203
      end
204

205
      should "setting a nil value for a param should exclude that param from the request" do
1✔
206
        stub_request(:get, "#{Stripe.api_base}/v1/charges")
1✔
207
          .with(query: { offset: 5, sad: false })
×
208
          .to_return(body: JSON.generate(count: 1, data: [charge_fixture]))
×
209
        Stripe::Charge.list(count: nil, offset: 5, sad: false)
1✔
210

211
        stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
212
          .with(body: { "amount" => "50", "currency" => "usd" })
×
213
          .to_return(body: JSON.generate(count: 1, data: [charge_fixture]))
×
214
        Stripe::Charge.create(amount: 50, currency: "usd", card: { number: nil })
1✔
215
      end
216

217
      should "not trigger a warning if a known opt, such as idempotency_key, is in opts" do
1✔
218
        stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
219
          .to_return(body: JSON.generate(charge_fixture))
×
220
        old_stderr = $stderr
1✔
221
        $stderr = StringIO.new
1✔
222
        begin
×
223
          Stripe::Charge.create({ amount: 100, currency: "usd", card: "sc_token" }, idempotency_key: "12345")
2✔
224
          assert $stderr.string.empty?
1✔
225
        ensure
226
          $stderr = old_stderr
1✔
227
        end
228
      end
229

230
      should "trigger a warning if a known opt, such as idempotency_key, is in params" do
1✔
231
        stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
232
          .to_return(body: JSON.generate(charge_fixture))
×
233
        old_stderr = $stderr
1✔
234
        $stderr = StringIO.new
1✔
235
        begin
×
236
          Stripe::Charge.create(amount: 100, currency: "usd", card: "sc_token", idempotency_key: "12345")
2✔
237
          assert_match Regexp.new("WARNING:"), $stderr.string
1✔
238
        ensure
239
          $stderr = old_stderr
1✔
240
        end
241
      end
242

243
      should "error if the params is not a Hash" do
1✔
244
        stub_request(:post, "#{Stripe.api_base}/v1/charges/ch_123/capture")
1✔
245
          .to_return(body: JSON.generate(charge_fixture))
×
246

247
        e = assert_raises(ArgumentError) { Stripe::Charge.capture("ch_123", "sk_test_secret") }
2✔
248

249
        assert_equal "request params should be either a Hash or nil (was a String)", e.message
1✔
250
      end
251

252
      should "allow making a request with params set to nil" do
1✔
253
        stub_request(:post, "#{Stripe.api_base}/v1/charges/ch_123/capture")
1✔
254
          .to_return(body: JSON.generate(charge_fixture))
×
255

256
        Stripe::Charge.capture("ch_123", nil, "sk_test_secret")
1✔
257
      end
258

259
      should "error if a user-specified opt is given a non-nil non-string value" do
1✔
260
        stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
261
          .to_return(body: JSON.generate(charge_fixture))
×
262

263
        # Works fine if not included or a string.
264
        Stripe::Charge.create({ amount: 100, currency: "usd" }, {})
1✔
265
        Stripe::Charge.create({ amount: 100, currency: "usd" }, idempotency_key: "12345")
1✔
266

267
        # Errors on a non-string.
268
        e = assert_raises(ArgumentError) do
1✔
269
          Stripe::Charge.create({ amount: 100, currency: "usd" }, idempotency_key: :foo)
1✔
270
        end
271
        assert_equal "request option 'idempotency_key' should be a string value " \
1✔
272
                     "(was a Symbol)",
273
                     e.message
×
274
      end
275

276
      should "requesting with a unicode ID should result in a request" do
1✔
277
        stub_request(:get, "#{Stripe.api_base}/v1/customers/%E2%98%83")
1✔
278
          .to_return(body: JSON.generate(make_missing_id_error), status: 404)
×
279
        c = Stripe::Customer.new("☃")
1✔
280
        assert_raises(Stripe::InvalidRequestError) { c.refresh }
2✔
281
      end
282

283
      should "requesting with no ID should result in an InvalidRequestError with no request" do
1✔
284
        c = Stripe::Customer.new
1✔
285
        assert_raises(Stripe::InvalidRequestError) { c.refresh }
2✔
286
      end
287

288
      should "making a GET request with parameters should have a query string and no body" do
1✔
289
        stub_request(:get, "#{Stripe.api_base}/v1/charges")
1✔
290
          .with(query: { limit: 1 })
×
291
          .to_return(body: JSON.generate(data: [charge_fixture]))
×
292
        Stripe::Charge.list(limit: 1)
1✔
293
      end
294

295
      should "making a POST request with parameters should have a body and no query string" do
1✔
296
        stub_request(:post, "#{Stripe.api_base}/v1/charges")
1✔
297
          .with(body: { "amount" => "100", "currency" => "usd", "card" => "sc_token" })
×
298
          .to_return(body: JSON.generate(charge_fixture))
×
299
        Stripe::Charge.create(amount: 100, currency: "usd", card: "sc_token")
1✔
300
      end
301

302
      should "loading an object should issue a GET request" do
1✔
303
        stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
1✔
304
          .to_return(body: JSON.generate(charge_fixture))
×
305
        c = Stripe::Charge.new("ch_123")
1✔
306
        c.refresh
1✔
307
      end
308

309
      should "using array accessors should be the same as the method interface" do
1✔
310
        stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
1✔
311
          .to_return(body: JSON.generate(charge_fixture))
×
312
        c = Stripe::Charge.new("cus_123")
1✔
313
        c.refresh
1✔
314
        assert_equal c.created, c[:created]
1✔
315
        assert_equal c.created, c["created"]
1✔
316
        c["created"] = 12_345
1✔
317
        assert_equal c.created, 12_345
1✔
318
      end
319

320
      should "saving an object should issue a POST request with only the changed properties" do
1✔
321
        stub_request(:post, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
322
          .with(body: { "description" => "another_mn" })
×
323
          .to_return(body: JSON.generate(customer_fixture))
×
324
        c = Stripe::Customer.construct_from(customer_fixture)
1✔
325
        c.description = "another_mn"
1✔
326
        c.save
1✔
327
      end
328

329
      should "updating an object should issue a POST request with the specified properties" do
1✔
330
        stub_request(:post, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
331
          .with(body: { "description" => "another_mn" })
×
332
          .to_return(body: JSON.generate(customer_fixture))
×
333
        Stripe::Customer.construct_from(customer_fixture)
1✔
334
        Stripe::Customer.update("cus_123", { description: "another_mn" })
1✔
335
      end
336

337
      should "saving should merge in returned properties" do
1✔
338
        stub_request(:post, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
339
          .with(body: { "description" => "another_mn" })
×
340
          .to_return(body: JSON.generate(customer_fixture))
×
341
        c = Stripe::Customer.new("cus_123")
1✔
342
        c.description = "another_mn"
1✔
343
        c.save
1✔
344
        assert_equal false, c.livemode
1✔
345
      end
346

347
      should "saving should fail if api_key is overwritten with nil" do
1✔
348
        c = Stripe::Customer.new
1✔
349
        assert_raises TypeError do
1✔
350
          c.save({}, api_key: nil)
1✔
351
        end
352
      end
353

354
      should "updating should fail if api_key is nil" do
1✔
355
        Stripe::Customer.new("cus_123")
1✔
356
        assert_raises TypeError do
1✔
357
          Stripe::Customer.update("cus_123", {}, { api_key: nil })
1✔
358
        end
359
      end
360

361
      should "saving should use the supplied api_key" do
1✔
362
        stub_request(:post, "#{Stripe.api_base}/v1/customers")
1✔
363
          .with(headers: { "Authorization" => "Bearer sk_test_local" })
×
364
          .to_return(body: JSON.generate(customer_fixture))
×
365
        c = Stripe::Customer.new
1✔
366
        c.save({}, api_key: "sk_test_local")
1✔
367
        assert_equal false, c.livemode
1✔
368
      end
369

370
      should "updating should use the supplied api_key" do
1✔
371
        stub_request(:post, "#{Stripe.api_base}/v1/customers")
1✔
372
          .with(headers: { "Authorization" => "Bearer sk_test_local" })
×
373
          .to_return(body: JSON.generate(customer_fixture))
×
374
        Stripe::Customer.new("cus_123")
1✔
375
        Stripe::Customer.update("cus_123", {}, api_key: "sk_test_local")
1✔
376
      end
377

378
      should "deleting should send no props and result in an object that has no props other deleted" do
1✔
379
        stub_request(:delete, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
380
          .to_return(body: JSON.generate("id" => "cus_123", "deleted" => true))
×
381
        c = Stripe::Customer.construct_from(customer_fixture)
1✔
382
        c.delete
1✔
383
      end
384

385
      should "loading all of an APIResource should return an array of recursively instantiated objects" do
1✔
386
        stub_request(:get, "#{Stripe.api_base}/v1/charges")
1✔
387
          .to_return(body: JSON.generate(data: [charge_fixture]))
×
388
        charges = Stripe::Charge.list.data
1✔
389
        assert charges.is_a? Array
1✔
390
        assert charges[0].is_a? Stripe::Charge
1✔
391
        assert charges[0].payment_method_details.is_a?(Stripe::StripeObject)
1✔
392
      end
393

394
      should "passing in a stripe_account header should pass it through on call" do
1✔
395
        stub_request(:get, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
396
          .with(headers: { "Stripe-Account" => "acct_123" })
×
397
          .to_return(body: JSON.generate(customer_fixture))
×
398
        Stripe::Customer.retrieve("cus_123", stripe_account: "acct_123")
1✔
399
      end
400

401
      should "passing in a stripe_account header should pass it through on save" do
1✔
402
        stub_request(:get, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
403
          .with(headers: { "Stripe-Account" => "acct_123" })
×
404
          .to_return(body: JSON.generate(customer_fixture))
×
405
        c = Stripe::Customer.retrieve("cus_123", stripe_account: "acct_123")
1✔
406

407
        stub_request(:post, "#{Stripe.api_base}/v1/customers/cus_123")
1✔
408
          .with(headers: { "Stripe-Account" => "acct_123" })
×
409
          .to_return(body: JSON.generate(customer_fixture))
×
410
        c.description = "FOO"
1✔
411
        c.save
1✔
412
      end
413

414
      should "add key to nested objects on save" do
1✔
415
        acct = Stripe::Account.construct_from(id: "myid",
1✔
416
                                              legal_entity: {
417
                                                size: "l",
418
                                                score: 4,
419
                                                height: 10,
420
                                              })
421

422
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
423
          .with(body: { legal_entity: { first_name: "Bob" } })
×
424
          .to_return(body: JSON.generate("id" => "myid"))
×
425

426
        acct.legal_entity.first_name = "Bob"
1✔
427
        acct.save
1✔
428
      end
429

430
      should "update with a nested object" do
1✔
431
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
432
          .with(body: { business_profile: { name: "Bob" } })
×
433
          .to_return(body: JSON.generate("id" => "myid"))
×
434

435
        Stripe::Account.update("myid", { business_profile: { name: "Bob" } })
1✔
436
      end
437

438
      should "save nothing if nothing changes" do
1✔
439
        acct = Stripe::Account.construct_from(id: "acct_id",
1✔
440
                                              metadata: {
441
                                                key: "value",
442
                                              })
443

444
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/acct_id")
1✔
445
          .with(body: {})
×
446
          .to_return(body: JSON.generate("id" => "acct_id"))
×
447

448
        acct.save
1✔
449
      end
450

451
      should "not save nested API resources" do
1✔
452
        ch = Stripe::Charge.construct_from(id: "ch_id",
1✔
453
                                           customer: {
454
                                             object: "customer",
455
                                             id: "customer_id",
456
                                           })
457

458
        stub_request(:post, "#{Stripe.api_base}/v1/charges/ch_id")
1✔
459
          .with(body: {})
×
460
          .to_return(body: JSON.generate("id" => "ch_id"))
×
461

462
        ch.customer.description = "Bob"
1✔
463
        ch.save
1✔
464
      end
465

466
      should "correctly handle replaced nested objects on save" do
1✔
467
        acct = Stripe::Account.construct_from(
1✔
468
          id: "acct_123",
469
          company: {
470
            name: "company_name",
471
            address: {
472
              line1: "test",
473
              city: "San Francisco",
474
            },
475
          }
476
        )
477

478
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/acct_123")
1✔
479
          .with(body: { company: { address: { line1: "Test2", city: "" } } })
×
480
          .to_return(body: JSON.generate("id" => "my_id"))
×
481

482
        acct.company.address = { line1: "Test2" }
1✔
483
        acct.save
1✔
484
      end
485

486
      should "correctly handle array setting on save" do
1✔
487
        acct = Stripe::Account.construct_from(id: "myid",
1✔
488
                                              legal_entity: {})
489

490
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
491
          .with(body: { legal_entity: { additional_owners: [{ first_name: "Bob" }] } })
×
492
          .to_return(body: JSON.generate("id" => "myid"))
×
493

494
        acct.legal_entity.additional_owners = [{ first_name: "Bob" }]
1✔
495
        acct.save
1✔
496
      end
497

498
      should "correctly handle array insertion on save" do
1✔
499
        acct = Stripe::Account.construct_from(id: "myid",
1✔
500
                                              legal_entity: {
501
                                                additional_owners: [],
502
                                              })
503

504
        # Note that this isn't a perfect check because we're using webmock's
505
        # data decoding, which isn't aware of the Stripe array encoding that we
506
        # use here.
507
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
508
          .with(body: { legal_entity: { additional_owners: [{ first_name: "Bob" }] } })
×
509
          .to_return(body: JSON.generate("id" => "myid"))
×
510

511
        acct.legal_entity.additional_owners << { first_name: "Bob" }
1✔
512
        acct.save
1✔
513
      end
514

515
      should "correctly handle array updates on save" do
1✔
516
        acct = Stripe::Account.construct_from(id: "myid",
1✔
517
                                              legal_entity: {
518
                                                additional_owners: [{ first_name: "Bob" }, { first_name: "Jane" }],
519
                                              })
520

521
        # Note that this isn't a perfect check because we're using webmock's
522
        # data decoding, which isn't aware of the Stripe array encoding that we
523
        # use here.
524
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
525
          .with(body: { legal_entity: { additional_owners: [{ first_name: "Janet" }] } })
×
526
          .to_return(body: JSON.generate("id" => "myid"))
×
527

528
        acct.legal_entity.additional_owners[1].first_name = "Janet"
1✔
529
        acct.save
1✔
530
      end
531

532
      should "correctly handle array noops on save" do
1✔
533
        acct = Stripe::Account.construct_from(id: "myid",
1✔
534
                                              legal_entity: {
535
                                                additional_owners: [{ first_name: "Bob" }],
536
                                              },
537
                                              currencies_supported: %w[usd cad])
538

539
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
540
          .with(body: {})
×
541
          .to_return(body: JSON.generate("id" => "myid"))
×
542

543
        acct.save
1✔
544
      end
545

546
      should "correctly handle hash noops on save" do
1✔
547
        acct = Stripe::Account.construct_from(id: "myid",
1✔
548
                                              legal_entity: {
549
                                                address: { line1: "1 Two Three" },
550
                                              })
551

552
        stub_request(:post, "#{Stripe.api_base}/v1/accounts/myid")
1✔
553
          .with(body: {})
×
554
          .to_return(body: JSON.generate("id" => "myid"))
×
555

556
        acct.save
1✔
557
      end
558

559
      should "should create a new resource when an object without an id is saved" do
1✔
560
        account = Stripe::Account.construct_from(id: nil,
1✔
561
                                                 display_name: nil)
562

563
        stub_request(:post, "#{Stripe.api_base}/v1/accounts")
1✔
564
          .with(body: { display_name: "stripe" })
×
565
          .to_return(body: JSON.generate("id" => "acct_123"))
×
566

567
        account.display_name = "stripe"
1✔
568
        account.save
1✔
569
      end
570

571
      should "set attributes as part of save" do
1✔
572
        account = Stripe::Account.construct_from(id: nil,
1✔
573
                                                 display_name: nil)
574

575
        stub_request(:post, "#{Stripe.api_base}/v1/accounts")
1✔
576
          .with(body: { display_name: "stripe", metadata: { key: "value" } })
×
577
          .to_return(body: JSON.generate("id" => "acct_123"))
×
578

579
        account.save(display_name: "stripe", metadata: { key: "value" })
1✔
580
      end
581
    end
582

583
    context "#request_stripe_object" do
1✔
584
      class HelloTestAPIResource < APIResource # rubocop:todo Lint/ConstantDefinitionInBlock
1✔
585
        OBJECT_NAME = "hello"
1✔
586
        def self.object_name
1✔
587
          "hello"
3✔
588
        end
589

590
        def say_hello(params = {}, opts = {})
1✔
591
          request_stripe_object(
3✔
592
            method: :post,
593
            path: resource_url + "/say",
2✔
594
            params: params,
595
            opts: opts
596
          )
597
        end
598
      end
599

600
      setup do
1✔
601
        Util.instance_variable_set(
6✔
602
          :@object_classes,
603
          Stripe::ObjectTypes.object_names_to_classes.merge(
2✔
604
            "hello" => HelloTestAPIResource
605
          )
606
        )
607
      end
608
      teardown do
1✔
609
        Util.class.instance_variable_set(:@object_classes, Stripe::ObjectTypes.object_names_to_classes)
3✔
610
      end
611

612
      should "make requests appropriately" do
1✔
613
        stub_request(:post, "#{Stripe.api_base}/v1/hellos/hi_123/say")
1✔
614
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
615
          .to_return(body: JSON.generate("object" => "hello"))
×
616

617
        hello = HelloTestAPIResource.new(id: "hi_123")
1✔
618
        hello.say_hello({ foo: "bar" }, stripe_account: "acct_hi")
1✔
619
      end
620

621
      should "update attributes in-place when it returns the same thing" do
1✔
622
        stub_request(:post, "#{Stripe.api_base}/v1/hellos/hi_123/say")
1✔
623
          .to_return(body: JSON.generate("object" => "hello", "additional" => "attribute"))
×
624

625
        hello = HelloTestAPIResource.new(id: "hi_123")
1✔
626
        hello.unsaved = "a value"
1✔
627
        new_hello = hello.say_hello
1✔
628

629
        # Doesn't matter if you use the return variable or the instance.
630
        assert_equal(hello, new_hello)
1✔
631

632
        # It updates new attributes in-place.
633
        assert_equal("attribute", hello.additional)
1✔
634

635
        # It removes unsaved attributes, but at least lets you know about them.
636
        e = assert_raises(NoMethodError) { hello.unsaved }
2✔
637
        assert_match("The 'unsaved' attribute was set in the past", e.message)
1✔
638
      end
639

640
      should "instantiate a new object of the appropriate class when it is different than the host class" do
1✔
641
        stub_request(:post, "#{Stripe.api_base}/v1/hellos/hi_123/say")
1✔
642
          .to_return(body: JSON.generate("object" => "goodbye", "additional" => "attribute"))
×
643

644
        hello = HelloTestAPIResource.new(id: "hi_123")
1✔
645
        hello.unsaved = "a value"
1✔
646
        new_goodbye = hello.say_hello
1✔
647

648
        # The returned value and the instance are different objects.
649
        refute_equal(new_goodbye, hello)
1✔
650

651
        # The returned value has stuff from the server.
652
        assert_equal("attribute", new_goodbye.additional)
1✔
653
        assert_equal("goodbye", new_goodbye.object)
1✔
654

655
        # You instance doesn't have stuff from the server.
656
        e = assert_raises(NoMethodError) { hello.additional }
2✔
657
        refute_match(/was set in the past/, e.message)
1✔
658

659
        # The instance preserves unset attributes on the original instance (not sure this is good behavior?)
660
        assert_equal("a value", hello.unsaved)
1✔
661
      end
662
    end
663

664
    context "#request_stream" do
1✔
665
      class StreamTestAPIResource < APIResource # rubocop:todo Lint/ConstantDefinitionInBlock
1✔
666
        OBJECT_NAME = "stream"
1✔
667
        def self.object_name
1✔
668
          "stream"
2✔
669
        end
670

671
        def read_stream(params = {}, opts = {}, &read_body_chunk_block)
1✔
672
          request_stream(
2✔
673
            method: :get,
674
            path: resource_url + "/read",
1✔
675
            params: params,
676
            opts: opts,
677
            &read_body_chunk_block
678
          )
679
        end
680
      end
681

682
      setup do
1✔
683
        Util.instance_variable_set(
4✔
684
          :@object_classes,
685
          Stripe::ObjectTypes.object_names_to_classes.merge(
1✔
686
            "stream" => StreamTestAPIResource
687
          )
688
        )
689
      end
690
      teardown do
1✔
691
        Util.class.instance_variable_set(:@object_classes, Stripe::ObjectTypes.object_names_to_classes)
2✔
692
      end
693

694
      should "supports requesting with a block" do
1✔
695
        stub_request(:get, "#{Stripe.api_base}/v1/streams/hi_123/read")
1✔
696
          .with(query: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
697
          .to_return(body: "response body")
×
698

699
        accumulated_body = +""
1✔
700

701
        resp = StreamTestAPIResource.new(id: "hi_123").read_stream({ foo: "bar" }, stripe_account: "acct_hi") do |body_chunk|
1✔
702
          accumulated_body << body_chunk
1✔
703
        end
704

705
        assert_instance_of Stripe::StripeHeadersOnlyResponse, resp
1✔
706
        assert_equal "response body", accumulated_body
1✔
707
      end
708

709
      should "fail when requesting without a block" do
1✔
710
        stub_request(:get, "#{Stripe.api_base}/v1/streams/hi_123/read")
1✔
711
          .with(query: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
712
          .to_return(body: "response body")
×
713

714
        assert_raises ArgumentError do
1✔
715
          StreamTestAPIResource.new(id: "hi_123").read_stream({ foo: "bar" }, stripe_account: "acct_hi")
1✔
716
        end
717
      end
718
    end
719

720
    context "custom API resource" do
1✔
721
      class ByeTestAPIResource < APIResource # rubocop:todo Lint/ConstantDefinitionInBlock
1✔
722
        OBJECT_NAME = "bye"
1✔
723

724
        def say_bye(params = {}, opts = {})
1✔
725
          request_stripe_object(
1✔
726
            method: :post,
727
            path: resource_url + "/say",
×
728
            params: params,
729
            opts: opts
730
          )
731
        end
732
      end
733

734
      should "make requests appropriately without a defined object_name method" do
1✔
735
        stub_request(:post, "#{Stripe.api_base}/v1/byes/bye_123/say")
1✔
736
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_bye" })
×
737
          .to_return(body: JSON.generate("object" => "bye"))
×
738

739
        bye = ByeTestAPIResource.new(id: "bye_123")
1✔
740
        bye.say_bye({ foo: "bar" }, stripe_account: "acct_bye")
1✔
741
      end
742
    end
743

744
    context "test helpers" do
1✔
745
      class TestHelperAPIResource < APIResource # rubocop:todo Lint/ConstantDefinitionInBlock
1✔
746
        OBJECT_NAME = "hello"
1✔
747

748
        def self.object_name
1✔
749
          "hello"
4✔
750
        end
751

752
        def test_helpers
1✔
753
          TestHelpers.new(self)
3✔
754
        end
755

756
        class TestHelpers < APIResourceTestHelpers
1✔
757
          RESOURCE_CLASS = TestHelperAPIResource
1✔
758

759
          def self.resource_class
1✔
760
            TestHelperAPIResource
5✔
761
          end
762

763
          custom_method :say_hello, http_verb: :post
1✔
764

765
          def say_hello(params = {}, opts = {})
1✔
766
            @resource.request_stripe_object(
6✔
767
              method: :post,
768
              path: resource_url + "/say_hello",
2✔
769
              params: params,
770
              opts: opts
771
            )
772
          end
773
        end
774
      end
775

776
      setup do
1✔
777
        Util.instance_variable_set(
8✔
778
          :@object_classes,
779
          Stripe::ObjectTypes.object_names_to_classes.merge(
3✔
780
            "hello" => TestHelperAPIResource
781
          )
782
        )
783
      end
784
      teardown do
1✔
785
        Util.class.instance_variable_set(:@object_classes, Stripe::ObjectTypes.object_names_to_classes)
4✔
786
      end
787

788
      should "make requests appropriately" do
1✔
789
        stub_request(:post, "#{Stripe.api_base}/v1/test_helpers/hellos/hi_123/say_hello")
1✔
790
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
791
          .to_return(body: JSON.generate("object" => "hello"))
×
792

793
        hello = TestHelperAPIResource.new(id: "hi_123")
1✔
794
        hello.test_helpers.say_hello({ foo: "bar" }, stripe_account: "acct_hi")
1✔
795
      end
796

797
      should "forward opts" do
1✔
798
        stub_request(:post, "#{Stripe.api_base}/v1/test_helpers/hellos/hi_123/say_hello")
1✔
799
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
800
          .to_return(body: JSON.generate("object" => "hello"))
×
801

802
        hello = TestHelperAPIResource.new("hi_123", stripe_account: "acct_hi")
1✔
803
        hello.test_helpers.say_hello({ foo: "bar" })
1✔
804
      end
805

806
      should "return resource" do
1✔
807
        stub_request(:post, "#{Stripe.api_base}/v1/test_helpers/hellos/hi_123/say_hello")
1✔
808
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
809
          .to_return(body: JSON.generate({ object: "hello", result: "hi!" }))
×
810

811
        hello = TestHelperAPIResource.new(id: "hi_123")
1✔
812
        new_hello = hello.test_helpers.say_hello({ foo: "bar" }, stripe_account: "acct_hi")
1✔
813
        assert new_hello.is_a? TestHelperAPIResource
1✔
814
        assert_equal "hi!", new_hello.result
1✔
815
      end
816

817
      should "be callable statically" do
1✔
818
        stub_request(:post, "#{Stripe.api_base}/v1/test_helpers/hellos/hi_123/say_hello")
1✔
819
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
820
          .to_return(body: JSON.generate({ object: "hello", result: "hi!" }))
×
821

822
        new_hello = TestHelperAPIResource::TestHelpers.say_hello("hi_123", { foo: "bar" }, stripe_account: "acct_hi")
1✔
823
        assert new_hello.is_a? TestHelperAPIResource
1✔
824
        assert_equal "hi!", new_hello.result
1✔
825
      end
826
    end
827

828
    context "singleton resource" do
1✔
829
      class TestSingletonResource < SingletonAPIResource # rubocop:todo Lint/ConstantDefinitionInBlock
1✔
830
        include Stripe::APIOperations::SingletonSave
1✔
831
        OBJECT_NAME = "test.singleton"
1✔
832

833
        def self.object_name
1✔
834
          "test.singleton"
4✔
835
        end
836
      end
837

838
      setup do
1✔
839
        Util.instance_variable_set(
6✔
840
          :@object_classes,
841
          Stripe::ObjectTypes.object_names_to_classes.merge(
2✔
842
            "test.singleton" => TestSingletonResource
843
          )
844
        )
845
      end
846
      teardown do
1✔
847
        Util.class.instance_variable_set(:@object_classes, Stripe::ObjectTypes.object_names_to_classes)
3✔
848
      end
849

850
      should "be retrievable" do
1✔
851
        stub_request(:get, "#{Stripe.api_base}/v1/test/singleton")
1✔
852
          .with(query: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
853
          .to_return(body: JSON.generate({ object: "test.singleton", result: "hi!" }))
×
854

855
        settings = TestSingletonResource.retrieve({ foo: "bar" }, { stripe_account: "acct_hi" })
1✔
856
        assert settings.is_a? TestSingletonResource
1✔
857
        assert_equal "hi!", settings.result
1✔
858
      end
859

860
      should "be updatable" do
1✔
861
        stub_request(:post, "#{Stripe.api_base}/v1/test/singleton")
1✔
862
          .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" })
×
863
          .to_return(body: JSON.generate({ object: "test.singleton", result: "hi!" }))
×
864

865
        settings = TestSingletonResource.update({ foo: "bar" }, { stripe_account: "acct_hi" })
1✔
866
        assert settings.is_a? TestSingletonResource
1✔
867
        assert_equal "hi!", settings.result
1✔
868
      end
869

870
      should "be saveable" do
1✔
871
        stub_request(:get, "#{Stripe.api_base}/v1/test/singleton")
1✔
872
          .to_return(body: JSON.generate({ object: "test.singleton", result: "hi!" }))
×
873

874
        stub_request(:post, "#{Stripe.api_base}/v1/test/singleton")
1✔
875
          .with(body: { result: "hello" })
×
876
          .to_return(body: JSON.generate({ object: "test.singleton", result: "hello" }))
×
877

878
        settings = TestSingletonResource.retrieve
1✔
879
        settings.result = "hello"
1✔
880
        settings.save
1✔
881
        assert_requested :post, "#{Stripe.api_base}/v1/test/singleton", times: 1
1✔
882
      end
883
    end
884

885
    @@fixtures = {} # rubocop:disable Style/ClassVars
1✔
886
    setup do
1✔
887
      if @@fixtures.empty?
68✔
888
        cache_fixture(:charge) do
1✔
889
          Charge.retrieve("ch_123")
1✔
890
        end
891
        cache_fixture(:customer) do
1✔
892
          Customer.retrieve("cus_123")
1✔
893
        end
894
      end
895
    end
896

897
    private def charge_fixture
1✔
898
      @@fixtures[:charge]
22✔
899
    end
900

901
    private def customer_fixture
1✔
902
      @@fixtures[:customer]
11✔
903
    end
904

905
    # Expects to retrieve a fixture from stripe-mock (an API call should be
906
    # included in the block to yield to) and does very simple memoization.
907
    private def cache_fixture(key)
1✔
908
      return @@fixtures[key] if @@fixtures.key?(key)
2✔
909

910
      obj = yield
2✔
911
      @@fixtures[key] = obj.instance_variable_get(:@values).freeze
2✔
912
      @@fixtures[key]
2✔
913
    end
914
  end
915
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc