gibbon 3.4.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.travis.yml +20 -9
- data/CHANGELOG.md +7 -1
- data/Gemfile +0 -8
- data/README.markdown +16 -37
- data/gibbon.gemspec +5 -6
- data/lib/gibbon/api_request.rb +5 -1
- data/lib/gibbon/version.rb +1 -1
- data/lib/gibbon.rb +0 -1
- data/spec/gibbon/api_request_spec.rb +129 -0
- data/spec/gibbon/gibbon_helpers_spec.rb +27 -0
- data/spec/gibbon/mailchimp_error_spec.rb +18 -0
- data/spec/gibbon/request_spec.rb +141 -0
- data/spec/gibbon/response_spec.rb +17 -0
- metadata +16 -23
- data/lib/gibbon/export.rb +0 -93
- data/spec/gibbon/export_spec.rb +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1cb1352737a9335535ba7d79420ec208817700b6e8b4fcf07a449499837b721f
|
|
4
|
+
data.tar.gz: 5fd15b6c055607e95a2e81f8b4fda95b606dc6be3ac7aa147057acd13c946a1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 20bd382a94c31402452f63037c0f266cdb291493b72b93b1aab2c15021fb108fbf9a45b4ced1f3070c2c2a18aaee3bd117ef98dc4b8846b3a07f44ac042c864f
|
|
7
|
+
data.tar.gz: a030570ce92ecd7744ee1ed8dead15f0a176e3b178fd0b410e4c11a870f90ebbc0ba5909a82ae59b55959cf1ae3c06f0fd612826b5fdf93a26954907aba96729
|
data/.github/FUNDING.yml
ADDED
data/.travis.yml
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
language: ruby
|
|
2
|
-
sudo: false
|
|
3
2
|
cache: bundler
|
|
4
|
-
before_install:
|
|
5
|
-
- gem install bundler # -v 1.7.14 if a specific version is needed
|
|
6
3
|
rvm:
|
|
7
|
-
-
|
|
8
|
-
- 2
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
- 3.1
|
|
5
|
+
- 3.2
|
|
6
|
+
- 3.3
|
|
7
|
+
- 3.4
|
|
8
|
+
jobs:
|
|
9
|
+
include:
|
|
10
|
+
# rvm's stable release does not know Ruby 4.0 yet, so boot on 3.4,
|
|
11
|
+
# then upgrade rvm to master and install 4.0 from there
|
|
12
|
+
- name: "4.0 (via rvm master)"
|
|
13
|
+
rvm: 3.4
|
|
14
|
+
before_install:
|
|
15
|
+
- rvm get master
|
|
16
|
+
- rvm install ruby-4.0
|
|
17
|
+
- rvm use ruby-4.0
|
|
18
|
+
- ruby -v
|
|
19
|
+
# Bundler 4 (shipped with Ruby 4.0) removed the --path flag that
|
|
20
|
+
# Travis's default install step still passes
|
|
21
|
+
install:
|
|
22
|
+
- bundle config set --local path vendor/bundle
|
|
23
|
+
- bundle install --jobs=3 --retry=3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [4.0.0] - 2026-07-23
|
|
2
|
+
- Require Ruby 3.1+ (drops support for EOL Rubies 2.4 through 3.0)
|
|
3
|
+
- Require TLS 1.2 as a minimum instead of pinning it exactly, allowing TLS 1.3
|
|
4
|
+
- Modernize development setup: loosen rspec pin, remove Rubinius leftovers, update CI matrix to Ruby 3.1 through 4.0
|
|
5
|
+
|
|
6
|
+
## [3.5.0] - 2023-06-07
|
|
7
|
+
- Remove support for retired Export API
|
|
2
8
|
|
|
3
9
|
## [3.4.4] - 2022-02-24
|
|
4
10
|
- Remove non-alpha characters when parsing datacenter from API keys to prevent potential attackers from injecting a domain via the API key. This would only be possible if one were using user-provided API keys (e.g. from a form, etc.).
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# gibbon
|
|
2
2
|
|
|
3
|
-
Gibbon is an API wrapper for MailChimp's [API](
|
|
3
|
+
Gibbon is an API wrapper for MailChimp's [API](https://mailchimp.com/developer/marketing/api/).
|
|
4
4
|
|
|
5
|
-
[](https://app.travis-ci.com/github/amro/gibbon)
|
|
5
|
+
[](https://app.travis-ci.com/github/amro/gibbon)
|
|
6
6
|
|
|
7
7
|
## Important Notes
|
|
8
8
|
|
|
9
|
-
Please read MailChimp's [Getting Started Guide](
|
|
9
|
+
Please read MailChimp's [Getting Started Guide](https://mailchimp.com/developer/marketing/guides/quick-start/).
|
|
10
10
|
|
|
11
11
|
Gibbon 3.0.0+ returns a `Gibbon::Response` instead of the response body directly. `Gibbon::Response` exposes the parsed response `body` and `headers`.
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ Gibbon 3.0.0+ returns a `Gibbon::Response` instead of the response body directly
|
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
|
-
A MailChimp account and API key. You can see your API keys [here](
|
|
19
|
+
A MailChimp account and API key. You can see your API keys [here](https://admin.mailchimp.com/account/api).
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
@@ -35,9 +35,9 @@ gibbon.timeout = 30
|
|
|
35
35
|
gibbon.open_timeout = 30
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
You can read about `timeout` and `open_timeout` in the [Net::HTTP](https://ruby-
|
|
38
|
+
You can read about `timeout` and `open_timeout` in the [Net::HTTP](https://docs.ruby-lang.org/en/master/Net/HTTP.html) doc.
|
|
39
39
|
|
|
40
|
-
Now you can make requests using the resources defined in [MailChimp's docs](
|
|
40
|
+
Now you can make requests using the resources defined in [MailChimp's docs](https://mailchimp.com/developer/marketing/api/). Resource IDs
|
|
41
41
|
are specified inline and a `CRUD` (`create`, `retrieve` (or `get`), `update`, `upsert`, or `delete`) verb initiates the request. `upsert` lets you update a record, if it exists, or insert it otherwise where supported by MailChimp's API.
|
|
42
42
|
|
|
43
43
|
***Note*** `upsert` requires Gibbon version 2.1.0 or newer!
|
|
@@ -84,7 +84,7 @@ Pass `symbolize_keys: true` to use symbols (instead of strings) as hash keys in
|
|
|
84
84
|
gibbon = Gibbon::Request.new(api_key: "your_api_key", symbolize_keys: true)
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
MailChimp's [resource documentation](
|
|
87
|
+
MailChimp's [resource documentation](https://mailchimp.com/developer/marketing/api/) is a list of available resources.
|
|
88
88
|
|
|
89
89
|
## Debug Logging
|
|
90
90
|
|
|
@@ -180,6 +180,12 @@ Get a specific member's information (open/click rates etc.) from MailChimp:
|
|
|
180
180
|
gibbon.lists(list_id).members(lower_case_md5_hashed_email_address).retrieve
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
Permanently delete a specific member from a list:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
gibbon.lists(list_id).members(lower_case_md5_hashed_email_address).actions.delete_permanent.create
|
|
187
|
+
```
|
|
188
|
+
|
|
183
189
|
### Tags
|
|
184
190
|
|
|
185
191
|
[Tags](https://mailchimp.com/help/getting-started-tags/) are a flexible way to organize (slice and dice) your list: for example, you can send a campaign directly to one or more tags.
|
|
@@ -235,7 +241,7 @@ gibbon.batches(batch_id).retrieve
|
|
|
235
241
|
```
|
|
236
242
|
|
|
237
243
|
***Note*** This response truncated for brevity. Reference the MailChimp
|
|
238
|
-
[API documentation for Batch Operations](
|
|
244
|
+
[API documentation for Batch Operations](https://mailchimp.com/developer/marketing/api/batches/) for more details.
|
|
239
245
|
|
|
240
246
|
### Fields
|
|
241
247
|
|
|
@@ -357,7 +363,7 @@ The response body (i.e. `response.body`) looks someting like this (unrelated thi
|
|
|
357
363
|
{"id"=>"...", "email_address"=>"...", ..., "interests"=>{"3def637141"=>true, "f7cc4ee841"=>false, "fcdc951b9f"=>false, "3daf3cf27d"=>true, "293a3703ed"=>false, "72370e0d1f"=>false, "d434d21a1c"=>false, "bdb1ff199f"=>false, "a54e78f203"=>false, "c4527fd018"=>false} ...}
|
|
358
364
|
```
|
|
359
365
|
|
|
360
|
-
The API returns a map of interest ID to boolean value. Now we to get interest details so we know what these interest IDs map to. Looking at [this doc page](
|
|
366
|
+
The API returns a map of interest ID to boolean value. Now we to get interest details so we know what these interest IDs map to. Looking at [this doc page](https://mailchimp.com/developer/marketing/api/interests/), we need to do this:
|
|
361
367
|
|
|
362
368
|
```ruby
|
|
363
369
|
gibbon.lists(list_id).interest_categories.retrieve
|
|
@@ -394,7 +400,7 @@ end
|
|
|
394
400
|
|
|
395
401
|
### Other
|
|
396
402
|
|
|
397
|
-
Overriding Gibbon's API endpoint (i.e. if using an access token from OAuth and have the `api_endpoint` from the [metadata](
|
|
403
|
+
Overriding Gibbon's API endpoint (i.e. if using an access token from OAuth and have the `api_endpoint` from the [metadata](https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/)):
|
|
398
404
|
|
|
399
405
|
```ruby
|
|
400
406
|
Gibbon::Request.api_endpoint = "https://us1.api.mailchimp.com"
|
|
@@ -474,33 +480,6 @@ Gibbon 2.x+:
|
|
|
474
480
|
```ruby
|
|
475
481
|
gibbon.lists(list_id).members.create(body: {email_address: "foo@bar.com", status: "subscribed", merge_fields: {FNAME: "Bob", LNAME: "Smith"}})
|
|
476
482
|
```
|
|
477
|
-
## Export API 1.0
|
|
478
|
-
|
|
479
|
-
Gibbon 3.0.0+ supports MailChimp's [Export API 1.0](https://apidocs.mailchimp.com/export/1.0/). You can choose to handle the API response all at
|
|
480
|
-
once or line by line by passing a block. To access the Export API with Gibbon, you must first create an instance of `Gibbon::Export`:
|
|
481
|
-
|
|
482
|
-
```ruby
|
|
483
|
-
export = Gibbon::Export.new(api_key: "your_api_key")
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
Next, call the method corresponding to the API endpoint you'd like to query:
|
|
487
|
-
|
|
488
|
-
```ruby
|
|
489
|
-
export.list(id: list_id)
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
This fetches and returns all of the results at once. Pass a block if you'd like to handle the response as individual lines:
|
|
493
|
-
|
|
494
|
-
```ruby
|
|
495
|
-
export = Gibbon::Export.new(api_key: "your_api_key", timeout: 1200)
|
|
496
|
-
export.list(id: list_id) do |row|
|
|
497
|
-
puts row
|
|
498
|
-
end
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
This is useful when handling large sets of data. Setting the `timeout` here is optional, but a high value is recommended given the nature of the Export API. The default timeout is 600 seconds.
|
|
502
|
-
|
|
503
|
-
Gibbon supports all current Export API endpoints: `/list`, `/ecommOrders`, and `/campaignSubscriberActivity`. They're mapped onto Ruby methods with similar names: `list()`, `ecomm_orders()`, and `campaign_subscriber_activity()` respectively. Please see [MailChimp's API documentation](https://developer.mailchimp.com/documentation/mailchimp/guides/how-to-use-the-export-api/) for supported parameters.
|
|
504
483
|
|
|
505
484
|
## Thanks
|
|
506
485
|
|
data/gibbon.gemspec
CHANGED
|
@@ -7,23 +7,22 @@ Gem::Specification.new do |s|
|
|
|
7
7
|
s.version = Gibbon::VERSION
|
|
8
8
|
s.authors = ["Amro Mousa"]
|
|
9
9
|
s.email = ["amromousa@gmail.com"]
|
|
10
|
-
s.homepage = "
|
|
10
|
+
s.homepage = "https://github.com/amro/gibbon"
|
|
11
11
|
|
|
12
|
-
s.summary = %q{A wrapper for MailChimp API 3.0
|
|
13
|
-
s.description = %q{A wrapper for MailChimp API 3.0
|
|
12
|
+
s.summary = %q{A wrapper for MailChimp API 3.0}
|
|
13
|
+
s.description = %q{A wrapper for MailChimp API 3.0}
|
|
14
14
|
s.license = "MIT"
|
|
15
15
|
|
|
16
16
|
s.files = `git ls-files`.split("\n")
|
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
18
|
s.require_paths = ["lib"]
|
|
20
|
-
s.required_ruby_version = '>=
|
|
19
|
+
s.required_ruby_version = '>= 3.1.0'
|
|
21
20
|
|
|
22
21
|
s.add_dependency('faraday', '>= 1.0')
|
|
23
22
|
s.add_dependency('multi_json', '>= 1.11.0')
|
|
24
23
|
|
|
25
24
|
s.add_development_dependency 'rake'
|
|
26
|
-
s.add_development_dependency "rspec", "3.
|
|
25
|
+
s.add_development_dependency "rspec", "~> 3.13"
|
|
27
26
|
s.add_development_dependency 'webmock', '~> 3.8'
|
|
28
27
|
|
|
29
28
|
end
|
data/lib/gibbon/api_request.rb
CHANGED
|
@@ -106,6 +106,10 @@ module Gibbon
|
|
|
106
106
|
# Helpers
|
|
107
107
|
|
|
108
108
|
def handle_error(error)
|
|
109
|
+
# Errors we raised ourselves (e.g. unparseable response bodies) already
|
|
110
|
+
# carry their details; re-wrapping them here would strip those attributes
|
|
111
|
+
raise error if error.is_a?(MailChimpError)
|
|
112
|
+
|
|
109
113
|
error_params = {}
|
|
110
114
|
|
|
111
115
|
begin
|
|
@@ -146,7 +150,7 @@ module Gibbon
|
|
|
146
150
|
end
|
|
147
151
|
|
|
148
152
|
def rest_client
|
|
149
|
-
client = Faraday.new(self.api_url, proxy: self.proxy, ssl: {
|
|
153
|
+
client = Faraday.new(self.api_url, proxy: self.proxy, ssl: { min_version: :TLS1_2 }) do |faraday|
|
|
150
154
|
faraday.response :raise_error
|
|
151
155
|
faraday.adapter adapter
|
|
152
156
|
if @request_builder.debug
|
data/lib/gibbon/version.rb
CHANGED
data/lib/gibbon.rb
CHANGED
|
@@ -66,4 +66,133 @@ describe Gibbon::APIRequest do
|
|
|
66
66
|
|
|
67
67
|
include_examples 'client error handling'
|
|
68
68
|
end
|
|
69
|
+
|
|
70
|
+
context 'Faraday::ServerError' do
|
|
71
|
+
let(:error_class) { Faraday::ServerError }
|
|
72
|
+
|
|
73
|
+
include_examples 'client error handling'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "error responses over HTTP" do
|
|
77
|
+
it "raises a MailChimpError with details parsed from a 4xx JSON response" do
|
|
78
|
+
error_body = '{"title": "Invalid Resource", "detail": "The resource submitted could not be validated.", "status": 422}'
|
|
79
|
+
stub_request(:get, "#{@api_root}/lists")
|
|
80
|
+
.with(basic_auth: @basic_auth_credentials)
|
|
81
|
+
.to_return(status: 422, body: error_body, headers: { 'Content-Type' => 'application/json' })
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
@gibbon.lists.retrieve
|
|
85
|
+
fail "expected a Gibbon::MailChimpError"
|
|
86
|
+
rescue Gibbon::MailChimpError => e
|
|
87
|
+
expect(e.status_code).to eq 422
|
|
88
|
+
expect(e.title).to eq "Invalid Resource"
|
|
89
|
+
expect(e.detail).to eq "The resource submitted could not be validated."
|
|
90
|
+
expect(e.raw_body).to eq error_body
|
|
91
|
+
expect(e.body).to eq MultiJson.load(error_body)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "raises a MailChimpError on a 5xx response" do
|
|
96
|
+
stub_request(:get, "#{@api_root}/lists")
|
|
97
|
+
.with(basic_auth: @basic_auth_credentials)
|
|
98
|
+
.to_return(status: 503, body: '{"title": "Service Unavailable"}')
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
@gibbon.lists.retrieve
|
|
102
|
+
fail "expected a Gibbon::MailChimpError"
|
|
103
|
+
rescue Gibbon::MailChimpError => e
|
|
104
|
+
expect(e.status_code).to eq 503
|
|
105
|
+
expect(e.title).to eq "Service Unavailable"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context "response parsing" do
|
|
111
|
+
it "returns a Gibbon::Response with the parsed body and headers" do
|
|
112
|
+
stub_request(:get, "#{@api_root}/lists")
|
|
113
|
+
.with(basic_auth: @basic_auth_credentials)
|
|
114
|
+
.to_return(status: 200, body: '{"lists": [{"id": "abc123"}]}', headers: { 'Content-Type' => 'application/json' })
|
|
115
|
+
|
|
116
|
+
response = @gibbon.lists.retrieve
|
|
117
|
+
expect(response).to be_a Gibbon::Response
|
|
118
|
+
expect(response.body).to eq({ "lists" => [{ "id" => "abc123" }] })
|
|
119
|
+
expect(response.headers["content-type"]).to eq "application/json"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "symbolizes response keys when symbolize_keys is true" do
|
|
123
|
+
stub_request(:get, "#{@api_root}/lists")
|
|
124
|
+
.with(basic_auth: @basic_auth_credentials)
|
|
125
|
+
.to_return(status: 200, body: '{"lists": [{"id": "abc123"}]}')
|
|
126
|
+
|
|
127
|
+
response = Gibbon::Request.new(api_key: api_key, symbolize_keys: true).lists.retrieve
|
|
128
|
+
expect(response.body).to eq({ lists: [{ id: "abc123" }] })
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "returns nil for an empty response body" do
|
|
132
|
+
stub_request(:get, "#{@api_root}/lists")
|
|
133
|
+
.with(basic_auth: @basic_auth_credentials)
|
|
134
|
+
.to_return(status: 200, body: "")
|
|
135
|
+
|
|
136
|
+
expect(@gibbon.lists.retrieve).to be_nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "raises a MailChimpError with details intact for an unparseable success response" do
|
|
140
|
+
stub_request(:get, "#{@api_root}/lists")
|
|
141
|
+
.with(basic_auth: @basic_auth_credentials)
|
|
142
|
+
.to_return(status: 200, body: "not json")
|
|
143
|
+
|
|
144
|
+
begin
|
|
145
|
+
@gibbon.lists.retrieve
|
|
146
|
+
fail "expected a Gibbon::MailChimpError"
|
|
147
|
+
rescue Gibbon::MailChimpError => e
|
|
148
|
+
expect(e.title).to eq "UNPARSEABLE_RESPONSE"
|
|
149
|
+
expect(e.status_code).to eq 500
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
context "configure_request" do
|
|
155
|
+
it "sets params, headers, body, and timeouts on the request" do
|
|
156
|
+
gibbon = Gibbon::Request.new(api_key: api_key, timeout: 45, open_timeout: 15)
|
|
157
|
+
api_request = Gibbon::APIRequest.new(builder: gibbon.lists)
|
|
158
|
+
request = Struct.new(:params, :headers, :body, :options).new({}, {}, nil, Faraday::RequestOptions.new)
|
|
159
|
+
|
|
160
|
+
api_request.send(:configure_request, request: request, params: { "count" => "10" }, headers: { "X-Custom" => "value" }, body: '{"a":1}')
|
|
161
|
+
|
|
162
|
+
expect(request.params).to eq({ "count" => "10" })
|
|
163
|
+
expect(request.headers).to eq({ "Content-Type" => "application/json", "X-Custom" => "value" })
|
|
164
|
+
expect(request.body).to eq '{"a":1}'
|
|
165
|
+
expect(request.options.timeout).to eq 45
|
|
166
|
+
expect(request.options.open_timeout).to eq 15
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context "rest_client configuration" do
|
|
171
|
+
it "includes the logger middleware when debug is enabled" do
|
|
172
|
+
gibbon = Gibbon::Request.new(api_key: api_key, debug: true)
|
|
173
|
+
client = Gibbon::APIRequest.new(builder: gibbon.lists).send(:rest_client)
|
|
174
|
+
expect(client.builder.handlers).to include(Faraday::Response::Logger)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "omits the logger middleware by default" do
|
|
178
|
+
client = Gibbon::APIRequest.new(builder: @gibbon.lists).send(:rest_client)
|
|
179
|
+
expect(client.builder.handlers).not_to include(Faraday::Response::Logger)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "configures the proxy" do
|
|
183
|
+
gibbon = Gibbon::Request.new(api_key: api_key, proxy: "http://proxy.example.com:8080")
|
|
184
|
+
client = Gibbon::APIRequest.new(builder: gibbon.lists).send(:rest_client)
|
|
185
|
+
expect(client.proxy.host).to eq "proxy.example.com"
|
|
186
|
+
expect(client.proxy.port).to eq 8080
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
context "rest_client ssl configuration" do
|
|
191
|
+
it "requires TLS 1.2 as a minimum without capping the TLS version" do
|
|
192
|
+
api_request = Gibbon::APIRequest.new(builder: @gibbon.lists)
|
|
193
|
+
client = api_request.send(:rest_client)
|
|
194
|
+
expect(client.ssl.min_version).to eq :TLS1_2
|
|
195
|
+
expect(client.ssl.max_version).to be_nil
|
|
196
|
+
end
|
|
197
|
+
end
|
|
69
198
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Gibbon::Helpers do
|
|
4
|
+
let(:helper) { Class.new { include Gibbon::Helpers }.new }
|
|
5
|
+
|
|
6
|
+
describe "#get_data_center_from_api_key" do
|
|
7
|
+
it "returns an empty string for a nil api key" do
|
|
8
|
+
expect(helper.get_data_center_from_api_key(nil)).to eq("")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "returns an empty string for an api key without a data center" do
|
|
12
|
+
expect(helper.get_data_center_from_api_key("1234")).to eq("")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns the data center with a trailing dot" do
|
|
16
|
+
expect(helper.get_data_center_from_api_key("1234-us1")).to eq("us1.")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "uses the last segment of a key containing multiple hyphens" do
|
|
20
|
+
expect(helper.get_data_center_from_api_key("12-34-us10")).to eq("us10.")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "strips non-alphanumeric characters to prevent domain injection" do
|
|
24
|
+
expect(helper.get_data_center_from_api_key("1234-attacker.net/path?")).to eq("attackernetpath.")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -46,4 +46,22 @@ describe Gibbon::MailChimpError do
|
|
|
46
46
|
it 'sets the status_code attribute' do
|
|
47
47
|
expect(@gibbon.status_code).to eq(params[:status_code])
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
context "without params" do
|
|
51
|
+
before do
|
|
52
|
+
@gibbon = Gibbon::MailChimpError.new(message)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "leaves the attributes nil" do
|
|
56
|
+
expect(@gibbon.title).to be_nil
|
|
57
|
+
expect(@gibbon.detail).to be_nil
|
|
58
|
+
expect(@gibbon.body).to be_nil
|
|
59
|
+
expect(@gibbon.raw_body).to be_nil
|
|
60
|
+
expect(@gibbon.status_code).to be_nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "still includes the attributes in the message" do
|
|
64
|
+
expect(@gibbon.message).to eq "Foo @title=nil, @detail=nil, @body=nil, @raw_body=nil, @status_code=nil"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
49
67
|
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'webmock/rspec'
|
|
3
|
+
|
|
4
|
+
describe Gibbon::Request do
|
|
5
|
+
let(:api_key) { "1234-us1" }
|
|
6
|
+
let(:api_root) { "https://us1.api.mailchimp.com/3.0" }
|
|
7
|
+
let(:basic_auth_credentials) { ['apikey', api_key] }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
@gibbon = Gibbon::Request.new(api_key: api_key)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "path building" do
|
|
14
|
+
it "builds a path from chained calls" do
|
|
15
|
+
@gibbon.lists("list123").members
|
|
16
|
+
expect(@gibbon.path).to eq("lists/list123/members")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "replaces underscores with hyphens" do
|
|
20
|
+
@gibbon.lists("list123").interest_categories
|
|
21
|
+
expect(@gibbon.path).to eq("lists/list123/interest-categories")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "downcases path parts" do
|
|
25
|
+
@gibbon.LISTS
|
|
26
|
+
expect(@gibbon.path).to eq("lists")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "responds to any method" do
|
|
30
|
+
expect(@gibbon.respond_to?(:anything_at_all)).to be true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "appends 'send' to the path when called without arguments" do
|
|
34
|
+
@gibbon.campaigns("abc").actions.send
|
|
35
|
+
expect(@gibbon.path).to eq("campaigns/abc/actions/send")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "dispatches send with arguments as a regular method call" do
|
|
39
|
+
expect(@gibbon.send(:api_key)).to eq(api_key)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "resets the path after a request" do
|
|
43
|
+
stub_request(:get, "#{api_root}/lists").with(basic_auth: basic_auth_credentials).to_return(status: 200)
|
|
44
|
+
@gibbon.lists.retrieve
|
|
45
|
+
expect(@gibbon.path).to eq("")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "resets the path when a request raises" do
|
|
49
|
+
stub_request(:get, "#{api_root}/lists").with(basic_auth: basic_auth_credentials).to_return(status: 500, body: "{}")
|
|
50
|
+
expect { @gibbon.lists.retrieve }.to raise_error(Gibbon::MailChimpError)
|
|
51
|
+
expect(@gibbon.path).to eq("")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "HTTP verb mapping" do
|
|
56
|
+
let(:body) { { name: "Foo" } }
|
|
57
|
+
|
|
58
|
+
it "maps create to POST" do
|
|
59
|
+
stub = stub_request(:post, "#{api_root}/lists")
|
|
60
|
+
.with(body: MultiJson.dump(body), basic_auth: basic_auth_credentials, headers: { 'Content-Type' => 'application/json' })
|
|
61
|
+
.to_return(status: 200)
|
|
62
|
+
@gibbon.lists.create(body: body)
|
|
63
|
+
expect(stub).to have_been_requested
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "maps update to PATCH" do
|
|
67
|
+
stub = stub_request(:patch, "#{api_root}/lists/list123")
|
|
68
|
+
.with(body: MultiJson.dump(body), basic_auth: basic_auth_credentials, headers: { 'Content-Type' => 'application/json' })
|
|
69
|
+
.to_return(status: 200)
|
|
70
|
+
@gibbon.lists("list123").update(body: body)
|
|
71
|
+
expect(stub).to have_been_requested
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "maps upsert to PUT" do
|
|
75
|
+
stub = stub_request(:put, "#{api_root}/lists/list123")
|
|
76
|
+
.with(body: MultiJson.dump(body), basic_auth: basic_auth_credentials, headers: { 'Content-Type' => 'application/json' })
|
|
77
|
+
.to_return(status: 200)
|
|
78
|
+
@gibbon.lists("list123").upsert(body: body)
|
|
79
|
+
expect(stub).to have_been_requested
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "maps retrieve to GET" do
|
|
83
|
+
stub = stub_request(:get, "#{api_root}/lists").with(basic_auth: basic_auth_credentials).to_return(status: 200)
|
|
84
|
+
@gibbon.lists.retrieve
|
|
85
|
+
expect(stub).to have_been_requested
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "maps get to GET" do
|
|
89
|
+
stub = stub_request(:get, "#{api_root}/lists").with(basic_auth: basic_auth_credentials).to_return(status: 200)
|
|
90
|
+
@gibbon.lists.get
|
|
91
|
+
expect(stub).to have_been_requested
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "maps delete to DELETE" do
|
|
95
|
+
stub = stub_request(:delete, "#{api_root}/lists/list123").with(basic_auth: basic_auth_credentials).to_return(status: 204)
|
|
96
|
+
@gibbon.lists("list123").delete
|
|
97
|
+
expect(stub).to have_been_requested
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe "request options" do
|
|
102
|
+
it "passes query params" do
|
|
103
|
+
stub = stub_request(:get, "#{api_root}/lists")
|
|
104
|
+
.with(query: { "count" => "10", "offset" => "20" }, basic_auth: basic_auth_credentials)
|
|
105
|
+
.to_return(status: 200)
|
|
106
|
+
@gibbon.lists.retrieve(params: { "count" => "10", "offset" => "20" })
|
|
107
|
+
expect(stub).to have_been_requested
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "passes custom headers" do
|
|
111
|
+
stub = stub_request(:get, "#{api_root}/lists")
|
|
112
|
+
.with(headers: { 'X-Custom' => 'custom-value' }, basic_auth: basic_auth_credentials)
|
|
113
|
+
.to_return(status: 200)
|
|
114
|
+
@gibbon.lists.retrieve(headers: { 'X-Custom' => 'custom-value' })
|
|
115
|
+
expect(stub).to have_been_requested
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe "api key handling" do
|
|
120
|
+
it "strips whitespace from the api key" do
|
|
121
|
+
gibbon = Gibbon::Request.new(api_key: " #{api_key} ")
|
|
122
|
+
expect(gibbon.api_key).to eq(api_key)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "class-level requests" do
|
|
127
|
+
before do
|
|
128
|
+
Gibbon::Request.api_key = api_key
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
after do
|
|
132
|
+
Gibbon::Request.api_key = nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "makes requests directly on the class" do
|
|
136
|
+
stub = stub_request(:get, "#{api_root}/lists").with(basic_auth: basic_auth_credentials).to_return(status: 200)
|
|
137
|
+
Gibbon::Request.lists.retrieve
|
|
138
|
+
expect(stub).to have_been_requested
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Gibbon::Response do
|
|
4
|
+
it "defaults body and headers to empty hashes" do
|
|
5
|
+
response = Gibbon::Response.new
|
|
6
|
+
expect(response.body).to eq({})
|
|
7
|
+
expect(response.headers).to eq({})
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "exposes the body and headers it is given" do
|
|
11
|
+
body = { "id" => "abc" }
|
|
12
|
+
headers = { "content-type" => "application/json" }
|
|
13
|
+
response = Gibbon::Response.new(body: body, headers: headers)
|
|
14
|
+
expect(response.body).to eq(body)
|
|
15
|
+
expect(response.headers).to eq(headers)
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gibbon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amro Mousa
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: faraday
|
|
@@ -56,16 +55,16 @@ dependencies:
|
|
|
56
55
|
name: rspec
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
|
-
- -
|
|
58
|
+
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 3.
|
|
60
|
+
version: '3.13'
|
|
62
61
|
type: :development
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
|
-
- -
|
|
65
|
+
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 3.
|
|
67
|
+
version: '3.13'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: webmock
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,7 +79,7 @@ dependencies:
|
|
|
80
79
|
- - "~>"
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
81
|
version: '3.8'
|
|
83
|
-
description: A wrapper for MailChimp API 3.0
|
|
82
|
+
description: A wrapper for MailChimp API 3.0
|
|
84
83
|
email:
|
|
85
84
|
- amromousa@gmail.com
|
|
86
85
|
executables: []
|
|
@@ -88,6 +87,7 @@ extensions: []
|
|
|
88
87
|
extra_rdoc_files: []
|
|
89
88
|
files:
|
|
90
89
|
- ".document"
|
|
90
|
+
- ".github/FUNDING.yml"
|
|
91
91
|
- ".gitignore"
|
|
92
92
|
- ".travis.yml"
|
|
93
93
|
- CHANGELOG.md
|
|
@@ -99,7 +99,6 @@ files:
|
|
|
99
99
|
- gibbon.gemspec
|
|
100
100
|
- lib/gibbon.rb
|
|
101
101
|
- lib/gibbon/api_request.rb
|
|
102
|
-
- lib/gibbon/export.rb
|
|
103
102
|
- lib/gibbon/gibbon_error.rb
|
|
104
103
|
- lib/gibbon/gibbon_helpers.rb
|
|
105
104
|
- lib/gibbon/mailchimp_error.rb
|
|
@@ -107,16 +106,17 @@ files:
|
|
|
107
106
|
- lib/gibbon/response.rb
|
|
108
107
|
- lib/gibbon/version.rb
|
|
109
108
|
- spec/gibbon/api_request_spec.rb
|
|
110
|
-
- spec/gibbon/
|
|
109
|
+
- spec/gibbon/gibbon_helpers_spec.rb
|
|
111
110
|
- spec/gibbon/gibbon_spec.rb
|
|
112
111
|
- spec/gibbon/mailchimp_error_spec.rb
|
|
112
|
+
- spec/gibbon/request_spec.rb
|
|
113
|
+
- spec/gibbon/response_spec.rb
|
|
113
114
|
- spec/gibbon/upsert_spec.rb
|
|
114
115
|
- spec/spec_helper.rb
|
|
115
|
-
homepage:
|
|
116
|
+
homepage: https://github.com/amro/gibbon
|
|
116
117
|
licenses:
|
|
117
118
|
- MIT
|
|
118
119
|
metadata: {}
|
|
119
|
-
post_install_message:
|
|
120
120
|
rdoc_options: []
|
|
121
121
|
require_paths:
|
|
122
122
|
- lib
|
|
@@ -124,21 +124,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
124
124
|
requirements:
|
|
125
125
|
- - ">="
|
|
126
126
|
- !ruby/object:Gem::Version
|
|
127
|
-
version:
|
|
127
|
+
version: 3.1.0
|
|
128
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
129
|
requirements:
|
|
130
130
|
- - ">="
|
|
131
131
|
- !ruby/object:Gem::Version
|
|
132
132
|
version: '0'
|
|
133
133
|
requirements: []
|
|
134
|
-
rubygems_version:
|
|
135
|
-
signing_key:
|
|
134
|
+
rubygems_version: 4.0.16
|
|
136
135
|
specification_version: 4
|
|
137
|
-
summary: A wrapper for MailChimp API 3.0
|
|
138
|
-
test_files:
|
|
139
|
-
- spec/gibbon/api_request_spec.rb
|
|
140
|
-
- spec/gibbon/export_spec.rb
|
|
141
|
-
- spec/gibbon/gibbon_spec.rb
|
|
142
|
-
- spec/gibbon/mailchimp_error_spec.rb
|
|
143
|
-
- spec/gibbon/upsert_spec.rb
|
|
144
|
-
- spec/spec_helper.rb
|
|
136
|
+
summary: A wrapper for MailChimp API 3.0
|
|
137
|
+
test_files: []
|
data/lib/gibbon/export.rb
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
require 'net/https'
|
|
2
|
-
|
|
3
|
-
module Gibbon
|
|
4
|
-
class Export
|
|
5
|
-
include Helpers
|
|
6
|
-
|
|
7
|
-
attr_accessor :api_key, :timeout
|
|
8
|
-
|
|
9
|
-
def initialize(api_key: nil, timeout: nil)
|
|
10
|
-
@api_key = api_key || self.class.api_key || ENV['MAILCHIMP_API_KEY']
|
|
11
|
-
@timeout = timeout || self.class.timeout || 600
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def list(params = {}, &block)
|
|
15
|
-
call("list", params, &block)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def ecomm_orders(params = {}, &block)
|
|
19
|
-
call("ecommOrders", params, &block)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def campaign_subscriber_activity(params = {}, &block)
|
|
23
|
-
call("campaignSubscriberActivity", params, &block)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
protected
|
|
27
|
-
|
|
28
|
-
def export_api_url
|
|
29
|
-
"https://#{get_data_center_from_api_key(@api_key)}api.mailchimp.com/export/1.0/"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def call(method, params = {}, &block)
|
|
33
|
-
rows = []
|
|
34
|
-
|
|
35
|
-
api_url = export_api_url + method + "/"
|
|
36
|
-
params = params.merge({ apikey: @api_key })
|
|
37
|
-
block = Proc.new { |row| rows << row } unless block_given?
|
|
38
|
-
|
|
39
|
-
ensure_api_key(params)
|
|
40
|
-
|
|
41
|
-
url = URI.parse(api_url)
|
|
42
|
-
req = Net::HTTP::Post.new(url.path, initheader = {'Content-Type' => 'application/json'})
|
|
43
|
-
req.body = MultiJson.dump(params)
|
|
44
|
-
Net::HTTP.start(url.host, url.port, read_timeout: @timeout, use_ssl: true, ssl_version: :TLSv1_2) do |http|
|
|
45
|
-
# http://stackoverflow.com/questions/29598196/ruby-net-http-read-body-nethttpokread-body-called-twice-ioerror
|
|
46
|
-
http.request req do |response|
|
|
47
|
-
i = -1
|
|
48
|
-
last = ''
|
|
49
|
-
response.read_body do |chunk|
|
|
50
|
-
next if chunk.nil? or chunk.strip.empty?
|
|
51
|
-
infix = "\n" if last[-1, 1]==']'
|
|
52
|
-
lines, last = try_parse_line("#{last}#{infix}#{chunk}")
|
|
53
|
-
lines.each { |line| block.call(line, i += 1) }
|
|
54
|
-
end
|
|
55
|
-
block.call(parse_line(last), i += 1) unless last.nil? or last.empty?
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
rows unless block_given?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def try_parse_line(res)
|
|
62
|
-
lines = res.split("\n")
|
|
63
|
-
last = lines.pop || ''
|
|
64
|
-
lines.map! { |line| parse_line(line) }
|
|
65
|
-
[lines.compact, last]
|
|
66
|
-
rescue MultiJson::ParseError
|
|
67
|
-
[[], last]
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def parse_line(line)
|
|
72
|
-
parsed_response = MultiJson.load(line)
|
|
73
|
-
rescue MultiJson::ParseError
|
|
74
|
-
return []
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
def ensure_api_key(params)
|
|
80
|
-
unless params[:apikey] && (get_data_center_from_api_key(params[:apikey]) != "")
|
|
81
|
-
raise Gibbon::GibbonError, "You must set an api_key prior to making a call"
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
class << self
|
|
86
|
-
attr_accessor :api_key, :timeout
|
|
87
|
-
|
|
88
|
-
def method_missing(sym, *args, &block)
|
|
89
|
-
new(api_key: self.api_key, timeout: self.timeout).send(sym, *args, &block)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
data/spec/gibbon/export_spec.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'webmock/rspec'
|
|
3
|
-
|
|
4
|
-
describe Gibbon::Export do
|
|
5
|
-
before do
|
|
6
|
-
Gibbon::Export.send(:public, *Gibbon::Export.protected_instance_methods)
|
|
7
|
-
@export = Gibbon::Export.new
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
it "doesn't allow empty api key" do
|
|
11
|
-
expect {@export.list(id: "123456")}.to raise_error(Gibbon::GibbonError)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it "doesn't allow api key without data center" do
|
|
15
|
-
@api_key = "123"
|
|
16
|
-
@export.api_key = @api_key
|
|
17
|
-
expect {@export.list(id: "123456")}.to raise_error(Gibbon::GibbonError)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it "sets an API key from the 'MAILCHIMP_API_KEY' ENV variable" do
|
|
21
|
-
ENV['MAILCHIMP_API_KEY'] = 'TESTKEY-us1'
|
|
22
|
-
@export = Gibbon::Export.new
|
|
23
|
-
expect(@export.api_key).to eq('TESTKEY-us1')
|
|
24
|
-
ENV.delete('MAILCHIMP_API_KEY')
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
it "sets correct endpoint from api key" do
|
|
28
|
-
@api_key = "TESTKEY-us1"
|
|
29
|
-
@export.api_key = @api_key
|
|
30
|
-
expect(@export.export_api_url).to eq("https://us1.api.mailchimp.com/export/1.0/")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it "sets correct timeout" do
|
|
34
|
-
@api_key = "TESTKEY-us1"
|
|
35
|
-
@export.api_key = @api_key
|
|
36
|
-
@export.timeout = 9
|
|
37
|
-
expect(@export.timeout).to eq(9)
|
|
38
|
-
end
|
|
39
|
-
end
|