gandi_v5 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef84fa55ae8d79f2d6fa178db059e4130ff8ae9ba2eb5f5886a57b9f965d8b48
4
- data.tar.gz: b5bc9448754bf08ab28c1e3235d01929a6edf3b82f00dbdf935b58d863035de1
3
+ metadata.gz: 02621cbb8405cbec872ddcf3d549770535377dc1e6b158e9488f6bddcf65a771
4
+ data.tar.gz: 73901bcd9e8cb66d5c56c1802915dc849bd66d17d8c013a2895f472ef93f9543
5
5
  SHA512:
6
- metadata.gz: 90083b929123d60702880dc1fbaa7500c972f38ce1134dbb031d1e5585cad63fbe7265ee38075e5329e2e125f4b441ef0bc02da79137444df4ea27c999c1fa26
7
- data.tar.gz: 536d1dae15301728cfda7df6c556913e75025d90f801c5748da598716b887bd0e8c4d25c2f0226ea25d9bab02fa9a3f185d64a5cf9a31694cb21a1ea5f8ac264
6
+ metadata.gz: e3caf30dc92addf609acb1e5e7f69841a5f896579f55c839813790200170222ede6df0e2c6448af2b3fa5848de5acb1a1cd50dbd1a19d538afc5c24e2d84e217
7
+ data.tar.gz: e1efdd6ed8ab463d7379b8040aeff61c62be58485464ed09e00bba3d28a4af9e27ac74f9333a74500f98e094b48832ed00439c01909fd9a1e5fb1a9677a7da76
data/.gitignore CHANGED
@@ -5,6 +5,7 @@ Gemfile.lock
5
5
  # And because this is Ruby, ignore the following
6
6
  # (source: https://github.com/github/gitignore/blob/master/Ruby.gitignore):
7
7
 
8
+ .ruby-version
8
9
  notes.txt
9
10
  todo.txt
10
11
  *.gem
data/.rubocop.yml CHANGED
@@ -8,13 +8,23 @@ Metrics/AbcSize:
8
8
  Max: 20
9
9
  Metrics/ClassLength:
10
10
  Max: 250
11
- Metrics/LineLength:
12
- Max: 100
11
+ Metrics/CyclomaticComplexity:
12
+ Max: 8
13
13
  Metrics/MethodLength:
14
14
  Max: 15
15
15
  Metrics/ModuleLength:
16
16
  Max: 200
17
+ Metrics/ParameterLists:
18
+ Max: 6
17
19
  Style/DocumentationMethod:
18
20
  Enabled: true
21
+ Style/HashEachMethods:
22
+ Enabled: true
23
+ Style/HashTransformKeys:
24
+ Enabled: true
25
+ Style/HashTransformValues:
26
+ Enabled: true
19
27
  Style/SignalException:
20
28
  Enabled: false
29
+ Layout/LineLength:
30
+ Max: 100
data/.travis.yml CHANGED
@@ -5,6 +5,10 @@ rvm:
5
5
  - 2.6.2
6
6
  - 2.6.3
7
7
  - 2.6.4
8
+ - 2.6.5
9
+ - 2.6.6
10
+ - 2.7.0
11
+ - 2.7.1
8
12
  gemfile:
9
13
  - Gemfile
10
14
  branches:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Gandi V5 API Gem Changelog
2
2
 
3
+ ## Version 0.4.0
4
+
5
+ * Fix exception when delete returns no content-type
6
+ * Add support for ruby 2.7.0
7
+ * Add up/downgrading mailbox offer
8
+ * Add dry run option to creating a mailbox
9
+ * Add sharing_id & dry run option for renewing domain
10
+ * Add listing customers under a reseller organization (GandiV5::Organization::Customer.list and GandiV5::Organization#customers)
11
+ * Add creating customer under a reseller organization (GandiV5::Organization::Customer.create and GandiV5::Organization#create_customer)
12
+
3
13
  ## Version 0.3.0
4
14
 
5
15
  * Additions to GandiV5::Domain
data/README.md CHANGED
@@ -8,7 +8,8 @@
8
8
  This gem supports the following versions of ruby, it may work on other versions but is not tested against them so don't rely on it.
9
9
 
10
10
  * ruby:
11
- * 2.6.0 - 2.6.4
11
+ * 2.6.0 - 2.6.6
12
+ * 2.7.0 - 2.7.1
12
13
  * jruby, once it's reached parity with ruby 2.6.x
13
14
  * truffleruby, once it's reached parity with ruby 2.6.x
14
15
  * rubinius, once it's reached parity with ruby 2.6.x
@@ -76,6 +77,7 @@ We follow the [Semantic Versioning](http://semver.org/) concept.
76
77
 
77
78
  | Gem Version | Gandi API Release Date |
78
79
  | --------------- | ---------------------- |
80
+ | 0.4.0 | 2019-10-01 |
79
81
  | 0.3.0 | 2019-08-22 |
80
82
  | 0.2.0 | 2019-05-16 |
81
83
  | 0.1.0 | 2019-05-16 |
data/lib/gandi_v5/data.rb CHANGED
@@ -179,9 +179,8 @@ class GandiV5
179
179
  define_method "#{name}=" do |value|
180
180
  instance_variable_set("@#{name}", value)
181
181
  end
182
- # rubocop:disable Style/AccessModifierDeclarations
182
+
183
183
  private "#{name}="
184
- # rubocop:enable Style/AccessModifierDeclarations
185
184
  end
186
185
 
187
186
  # @api private
@@ -211,10 +211,13 @@ class GandiV5
211
211
  # @param duration [Integer, #to_s] how long to renew for (in years).
212
212
  # @return [String] confirmation message from Gandi.
213
213
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
214
- def renew_for(duration = 1)
214
+ def renew_for(duration = 1, sharing_id: nil, dry_run: false)
215
215
  body = { duration: duration }.to_json
216
- _response, data = GandiV5.post url('renew'), body
217
- data['message']
216
+ url_ = url('renew')
217
+ url_ = sharing_id ? "#{url_}?sharing_id=#{sharing_id}" : url_
218
+
219
+ _response, data = GandiV5.post(url_, body, 'Dry-Run': dry_run ? 1 : 0)
220
+ dry_run ? data : data['message']
218
221
  end
219
222
 
220
223
  # Restoration information for the domain.
@@ -105,6 +105,39 @@ class GandiV5
105
105
  data['message']
106
106
  end
107
107
 
108
+ # Upgrade a standard mailbox to premium.
109
+ # If the current slot is a free one, a new premium slot is created and
110
+ # used for the mailbox. Otherwise, the slot is upgraded to premium.
111
+ # @see https://api.gandi.net/docs/email#patch-v5-email-mailboxes-domain-mailbox_id-type
112
+ # @param sharing_id [String, #to_s, nil] (optional)
113
+ # the organisation ID to bill for the mailbox.
114
+ # @param dry_run [Boolean] whether the details should be checked instead
115
+ # of actually upgrading the mailbox.
116
+ # @return [true] if the mailbox was upgraded
117
+ # @return [false] if the mailbox was not upgraded (it's already premium)
118
+ # @return [Hash] if doing a dry run, you get what Gandi returns
119
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
120
+ def upgrade(sharing_id: nil, dry_run: false)
121
+ patch_type :premium, sharing_id, dry_run
122
+ end
123
+
124
+ # Downgrade a premium mailbox to standard.
125
+ # If a free slot is available, the premium slot is destroyed
126
+ # (and refunded) and the free one is used for the mailbox.
127
+ # Otherwise, the slot is downgraded to standard.
128
+ # @see https://api.gandi.net/docs/email#patch-v5-email-mailboxes-domain-mailbox_id-type
129
+ # @param sharing_id [String, #to_s, nil] (optional)
130
+ # the organisation ID to bill for the mailbox.
131
+ # @param dry_run [Boolean] whether the details should be checked instead
132
+ # of actually downgrading the mailbox.
133
+ # @return [true] if the mailbox was downgraded
134
+ # @return [false] if the mailbox was not downgraded (it's already standard)
135
+ # @return [Hash] if doing a dry run, you get what Gandi returns
136
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
137
+ def downgrade(sharing_id: nil, dry_run: false)
138
+ patch_type :standard, sharing_id, dry_run
139
+ end
140
+
108
141
  # Create a new mailbox.
109
142
  # Note that before you can create a mailbox, you must have a slot available.
110
143
  # @see https://api.gandi.net/docs/email#post-v5-email-mailboxes-domain
@@ -113,10 +146,13 @@ class GandiV5
113
146
  # @param password [String, #to_s] the password to use.
114
147
  # @param aliases [Array<String, #to_s>] any alternative email address to be used.
115
148
  # @param type [:standard, :premium] the type of mailbox slot to use.
149
+ # @param dry_run [Boolean] whether the details should be checked instead
150
+ # of actually creating the mailbox.
116
151
  # @return [GandiV5::Email::Mailbox] The created mailbox.
117
152
  # @raise [GandiV5::Error] if no slots are available.
118
153
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
119
- def self.create(fqdn, login, password, aliases: [], type: :standard)
154
+ # rubocop:disable Metrics/AbcSize
155
+ def self.create(fqdn, login, password, aliases: [], type: :standard, dry_run: false)
120
156
  fail ArgumentError, "#{type.inspect} is not a valid type" unless TYPES.include?(type)
121
157
  if GandiV5::Email::Slot.list.none? { |slot| slot.mailbox_type == type && slot.inactive? }
122
158
  fail GandiV5::Error, "no available #{type} slots"
@@ -131,9 +167,11 @@ class GandiV5
131
167
  aliases: aliases.push
132
168
  }.to_json
133
169
 
134
- response, _data = GandiV5.post url(fqdn), body
135
- fetch fqdn, response.headers[:location].split('/').last
170
+ response, data = GandiV5.post(url(fqdn), body, 'Dry-Run': dry_run ? 1 : 0)
171
+
172
+ dry_run ? data : fetch(fqdn, response.headers[:location].split('/').last)
136
173
  end
174
+ # rubocop:enable Metrics/AbcSize
137
175
 
138
176
  # Get information for a mailbox.
139
177
  # @see https://api.gandi.net/docs/email#get-v5-email-mailboxes-domain-mailbox_id
@@ -240,6 +278,20 @@ class GandiV5
240
278
  def crypt_password(password)
241
279
  self.class.send :crypt_password, password
242
280
  end
281
+
282
+ def patch_type(new_type, sharing_id, dry_run)
283
+ fail ArgumentError unless TYPES.include?(new_type)
284
+ return false if type == new_type
285
+
286
+ url_ = "#{url}/type"
287
+ url_ = sharing_id ? "#{url_}?sharing_id=#{sharing_id}" : url_
288
+ body = { mailbox_type: new_type }
289
+
290
+ _response, data = GandiV5.patch(url_, body.to_json, 'Dry-Run': dry_run ? 1 : 0)
291
+
292
+ @type = new_type unless dry_run
293
+ dry_run ? data : true
294
+ end
243
295
  end
244
296
  end
245
297
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Organization
5
+ # A customer of a reseller organization
6
+ # @!attribute [r] id
7
+ # @return [String] The main identifier of the customer.
8
+ # Also known as sharing_id in many routes.
9
+ # @!attribute [r] email
10
+ # @return [String] Email of the customer.
11
+ # @!attribute [r] first_name
12
+ # @return [String] First name of the customer.
13
+ # @!attribute [r] last_name
14
+ # @return [String] Last name of the customer.
15
+ # @!attribute [r] name
16
+ # @return [String] Name of the customer.
17
+ # @!attribute [r] type
18
+ # @return [:individual, :company, :association, :publicbody]
19
+ # @!attribute [r] org_name
20
+ # @return [nil, String] Organization legal name of the customer..
21
+ class Customer
22
+ include GandiV5::Data
23
+
24
+ members :email, :name
25
+ member :uuid, gandi_key: 'id'
26
+ member :first_name, gandi_key: 'firstname'
27
+ member :last_name, gandi_key: 'lastname'
28
+ member :org_name, gandi_key: 'orgname'
29
+ member :type, converter: GandiV5::Data::Converter::Symbol
30
+
31
+ # Create a new customer for this organization.
32
+ # @see https://api.gandi.net/docs/organization/#post-v5-organization-organizations-id-customers
33
+ # @param org_uuid [String] UUID of the organization to create the customer for.
34
+ # @param firstname [String, #to_s] (required) Customer's first name.
35
+ # @param lastname [String, #to_s] (required) Customer's last name.
36
+ # @param type [String, #to_s] (required) Type of customer
37
+ # ("individual", "company", "association" or "publicbody").
38
+ # @param streetaddr [String, #to_s] (required) Customer's street address.
39
+ # @param city [String, #to_s] (required) Customer's city.
40
+ # @param country [String, #to_s] (required) Customer's country.
41
+ # @param email [String, #to_s] (required) Customer's email address.
42
+ # @param phone [String, #to_s] (required) Customer's phone number.
43
+ # @param fax [String, #to_s] (optional) Customer's fax number.
44
+ # @param streetaddr2 [String, #to_s] (optional) Customer's street address (2nd line).
45
+ # @param state [String, #to_s] (optional) Customer's state/province/region.
46
+ # @param zip [String, #to_s] (optional) Customer's postal/zip code.
47
+ # @param reference [String, #to_s] (optional)
48
+ # Optional text to display on the invoice, such as your own customer reference info.
49
+ # @param orgname [String, #to_s] (optional) Customer Organization's legal name.
50
+ # @return [nil]
51
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
52
+ def self.create(org_uuid, **params)
53
+ %i[city country email firstname lastname phone streetaddr type].each do |attr|
54
+ fail ArgumentError, "missing keyword: #{attr}" unless params.key?(attr)
55
+ end
56
+ unless %w[individual company association publicbody].include?(params[:type].to_s)
57
+ fail ArgumentError, "invalid type: #{params[:type].inspect}"
58
+ end
59
+
60
+ _response, _data = GandiV5.post(url(org_uuid), params.to_json)
61
+ nil
62
+ end
63
+
64
+ # List organisation's customers.
65
+ # @see https://api.gandi.net/docs/organization/#get-v5-organization-organizations-id-customers
66
+ # @param org_uuid [String] UUID of the organization to fetch customers for.
67
+ # @param name [String, #to_s] (optional)
68
+ # filters the list by name, with optional patterns.
69
+ # e.g. "alice", "ali*", "*ice"
70
+ # @param permission [String, #to_s] (optional)
71
+ # filters the list by the permission the authenticated user has on
72
+ # that organization and products in it.
73
+ # @param sort_by [String, #to_s] (optional default "name") how to sort the list.
74
+ # @return [Array<GandiV5::Organization>]
75
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
76
+ def self.list(org_uuid, **params)
77
+ params['~name'] = params.delete(:name) if params.key?(:name)
78
+ _resp, data = GandiV5.get url(org_uuid), params: params
79
+ data.map { |organisation| from_gandi organisation }
80
+ end
81
+
82
+ private
83
+
84
+ def self.url(org_uuid)
85
+ "#{BASE}organization/organizations/#{org_uuid}/customers"
86
+ end
87
+ private_class_method :url
88
+ end
89
+ end
90
+ end
@@ -50,6 +50,16 @@ class GandiV5
50
50
 
51
51
  alias organization_uuid uuid
52
52
 
53
+ # @see GandiV5::Organization::Customer.list
54
+ def customers(org_uuid, **params)
55
+ GandiV5::Organization::Customer.list(org_uuid, **params)
56
+ end
57
+
58
+ # @see GandiV5::Organization::Customer.create
59
+ def create_customer(org_uuid, **params)
60
+ GandiV5::Organization::Customer.create(org_uuid, **params)
61
+ end
62
+
53
63
  # Get information about the current authenticated user.
54
64
  # @see https://api.gandi.net/docs/organization#get-v5-organization-user-info
55
65
  # @return [GandiV5::Organization]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class GandiV5
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/gandi_v5.rb CHANGED
@@ -93,7 +93,10 @@ class GandiV5
93
93
  def delete(url, **headers)
94
94
  prepare_headers headers, url
95
95
  response = RestClient.delete url, **headers
96
- [response, parse_response(response)]
96
+ [
97
+ response,
98
+ response.headers.key?(:content_type) ? parse_response(response) : nil
99
+ ]
97
100
  rescue RestClient::BadRequest => e
98
101
  handle_bad_request(e)
99
102
  end
data/spec/.rubocop.yml CHANGED
@@ -1,4 +1,11 @@
1
1
  Metrics/BlockLength:
2
2
  Max: 750
3
- Metrics/LineLength:
3
+ Layout/LineLength:
4
4
  Max: 125
5
+ Style/HashEachMethods:
6
+ Enabled: true
7
+ Style/HashTransformKeys:
8
+ Enabled: true
9
+ Style/HashTransformValues:
10
+ Enabled: true
11
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ - id: customer-uuid
3
+ name: FirstLast
4
+ firstname: First
5
+ lastname: Last
6
+ email: first.last@example.com
7
+ type: individual
8
+ orgname: Org
@@ -373,10 +373,46 @@ describe GandiV5::Domain do
373
373
  its('durations') { should match_array [1, 2] }
374
374
  end
375
375
 
376
- it '#renew_for' do
377
- expect(GandiV5).to receive(:post).with('https://api.gandi.net/v5/domain/domains/example.com/renew', '{"duration":2}')
378
- .and_return([nil, { 'message' => 'Confirmation message.' }])
379
- expect(subject.renew_for(2)).to eq 'Confirmation message.'
376
+ describe '#renew_for' do
377
+ it 'Defaults to 1 year and current user' do
378
+ expect(GandiV5).to receive(:post).with(
379
+ 'https://api.gandi.net/v5/domain/domains/example.com/renew',
380
+ '{"duration":1}',
381
+ 'Dry-Run': 0
382
+ )
383
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
384
+ expect(subject.renew_for).to eq 'Confirmation message.'
385
+ end
386
+
387
+ it 'With provided duration' do
388
+ expect(GandiV5).to receive(:post).with(
389
+ 'https://api.gandi.net/v5/domain/domains/example.com/renew',
390
+ '{"duration":2}',
391
+ 'Dry-Run': 0
392
+ )
393
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
394
+ expect(subject.renew_for(2)).to eq 'Confirmation message.'
395
+ end
396
+
397
+ it 'With provided sharing_id' do
398
+ expect(GandiV5).to receive(:post).with(
399
+ 'https://api.gandi.net/v5/domain/domains/example.com/renew?sharing_id=def',
400
+ '{"duration":1}',
401
+ 'Dry-Run': 0
402
+ )
403
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
404
+ expect(subject.renew_for(sharing_id: 'def')).to eq 'Confirmation message.'
405
+ end
406
+
407
+ it 'Does a dry run' do
408
+ expect(GandiV5).to receive(:post).with(
409
+ 'https://api.gandi.net/v5/domain/domains/example.com/renew',
410
+ '{"duration":1}',
411
+ 'Dry-Run': 1
412
+ )
413
+ .and_return([nil, { 'status' => 'success' }])
414
+ expect(subject.renew_for(dry_run: true)).to eq('status' => 'success')
415
+ end
380
416
  end
381
417
 
382
418
  describe '#renewal_price' do
@@ -87,6 +87,108 @@ describe GandiV5::Email::Mailbox do
87
87
  end
88
88
  end
89
89
 
90
+ describe '#upgrade' do
91
+ let(:url) { 'https://api.gandi.net/v5/email/mailboxes/example.com/mailbox-uuid/type' }
92
+
93
+ context 'No sharing_id' do
94
+ it 'Is upgraded' do
95
+ expect(GandiV5).to receive(:patch).with(url, '{"mailbox_type":"premium"}', 'Dry-Run': 0)
96
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
97
+ expect(subject.upgrade).to be true
98
+ expect(subject.type).to be :premium
99
+ end
100
+
101
+ it 'Is already premium' do
102
+ subject.instance_exec { @type = :premium }
103
+ expect(GandiV5).to_not receive(:patch)
104
+ expect(subject.upgrade).to be false
105
+ end
106
+ end
107
+
108
+ context 'With sharing_id' do
109
+ let(:url) { 'https://api.gandi.net/v5/email/mailboxes/example.com/mailbox-uuid/type?sharing_id=abc' }
110
+
111
+ it 'Is upgraded' do
112
+ expect(GandiV5).to receive(:patch).with(url, '{"mailbox_type":"premium"}', 'Dry-Run': 0)
113
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
114
+ expect(subject.upgrade(sharing_id: 'abc')).to be true
115
+ expect(subject.type).to be :premium
116
+ end
117
+
118
+ it 'Is already premium' do
119
+ subject.instance_exec { @type = :premium }
120
+ expect(GandiV5).to_not receive(:patch)
121
+ expect(subject.upgrade(sharing_id: 'abc')).to be false
122
+ end
123
+ end
124
+
125
+ context 'Dry run' do
126
+ it 'Is upgraded' do
127
+ expect(GandiV5).to receive(:patch).with(url, '{"mailbox_type":"premium"}', 'Dry-Run': 1)
128
+ .and_return([nil, { 'status' => 'success' }])
129
+ expect(subject.upgrade(dry_run: true)).to eq('status' => 'success')
130
+ expect(subject.type).to be :standard
131
+ end
132
+
133
+ it 'Is already premium' do
134
+ subject.instance_exec { @type = :premium }
135
+ expect(GandiV5).to_not receive(:patch)
136
+ expect(subject.upgrade(dry_run: true)).to be false
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#downgrade' do
142
+ let(:url) { 'https://api.gandi.net/v5/email/mailboxes/example.com/mailbox-uuid/type' }
143
+
144
+ context 'No sharing_id' do
145
+ it 'Is downgraded' do
146
+ subject.instance_exec { @type = :premium }
147
+ expect(GandiV5).to receive(:patch).with(url, '{"mailbox_type":"standard"}', 'Dry-Run': 0)
148
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
149
+ expect(subject.downgrade).to be true
150
+ expect(subject.type).to be :standard
151
+ end
152
+
153
+ it 'Is already premium' do
154
+ expect(GandiV5).to_not receive(:patch)
155
+ expect(subject.downgrade).to be false
156
+ end
157
+ end
158
+
159
+ context 'With sharing_id' do
160
+ let(:url) { 'https://api.gandi.net/v5/email/mailboxes/example.com/mailbox-uuid/type?sharing_id=abc' }
161
+
162
+ it 'Is downgraded' do
163
+ subject.instance_exec { @type = :premium }
164
+ expect(GandiV5).to receive(:patch).with(url, '{"mailbox_type":"standard"}', 'Dry-Run': 0)
165
+ .and_return([nil, { 'message' => 'Confirmation message.' }])
166
+ expect(subject.downgrade(sharing_id: 'abc')).to be true
167
+ expect(subject.type).to be :standard
168
+ end
169
+
170
+ it 'Is already premium' do
171
+ expect(GandiV5).to_not receive(:patch)
172
+ expect(subject.downgrade(sharing_id: 'abc')).to be false
173
+ end
174
+ end
175
+
176
+ context 'Dry run' do
177
+ it 'Is downgraded' do
178
+ subject.instance_exec { @type = :premium }
179
+ expect(GandiV5).to receive(:patch).with(url, '{"mailbox_type":"standard"}', 'Dry-Run': 1)
180
+ .and_return([nil, { 'status' => 'success' }])
181
+ expect(subject.downgrade(dry_run: true)).to eq('status' => 'success')
182
+ expect(subject.type).to be :premium
183
+ end
184
+
185
+ it 'Is already premium' do
186
+ expect(GandiV5).to_not receive(:patch)
187
+ expect(subject.downgrade(dry_run: true)).to be false
188
+ end
189
+ end
190
+ end
191
+
90
192
  it '#delete' do
91
193
  url = 'https://api.gandi.net/v5/email/mailboxes/example.com/mailbox-uuid'
92
194
  expect(GandiV5).to receive(:delete).with(url)
@@ -204,7 +306,7 @@ describe GandiV5::Email::Mailbox do
204
306
 
205
307
  it 'No aliases and :standard type' do
206
308
  body = '{"mailbox_type":"standard","login":"login","password":"crypted_password","aliases":[]}'
207
- expect(GandiV5).to receive(:post).with(url, body)
309
+ expect(GandiV5).to receive(:post).with(url, body, 'Dry-Run': 0)
208
310
  .and_return([created_response, { 'message' => 'Confirmation message.' }])
209
311
  expect(described_class).to receive(:crypt_password).with(good_password).and_return('crypted_password')
210
312
  expect(described_class).to receive(:fetch).with('example.com', 'created-mailbox-uuid').and_return(created_mailbox)
@@ -214,7 +316,7 @@ describe GandiV5::Email::Mailbox do
214
316
 
215
317
  it 'With aliases' do
216
318
  body = '{"mailbox_type":"standard","login":"login","password":"crypted_password","aliases":["alias-1"]}'
217
- expect(GandiV5).to receive(:post).with(url, body)
319
+ expect(GandiV5).to receive(:post).with(url, body, 'Dry-Run': 0)
218
320
  .and_return([created_response, { 'message' => 'Confirmation message.' }])
219
321
  expect(described_class).to receive(:crypt_password).with(good_password).and_return('crypted_password')
220
322
  expect(described_class).to receive(:fetch).with('example.com', 'created-mailbox-uuid').and_return(created_mailbox)
@@ -224,7 +326,7 @@ describe GandiV5::Email::Mailbox do
224
326
 
225
327
  it 'With different type' do
226
328
  body = '{"mailbox_type":"premium","login":"login","password":"crypted_password","aliases":[]}'
227
- expect(GandiV5).to receive(:post).with(url, body)
329
+ expect(GandiV5).to receive(:post).with(url, body, 'Dry-Run': 0)
228
330
  .and_return([created_response, { 'message' => 'Confirmation message.' }])
229
331
  expect(described_class).to receive(:crypt_password).with(good_password).and_return('crypted_password')
230
332
  expect(described_class).to receive(:fetch).with('example.com', 'created-mailbox-uuid').and_return(created_mailbox)
@@ -254,6 +356,16 @@ describe GandiV5::Email::Mailbox do
254
356
  'no available standard slots'
255
357
  )
256
358
  end
359
+
360
+ it 'Doing a dry run' do
361
+ body = '{"mailbox_type":"standard","login":"login","password":"crypted_password","aliases":[]}'
362
+ expect(GandiV5).to receive(:post).with(url, body, 'Dry-Run': 1)
363
+ .and_return([nil, { 'status' => 'success' }])
364
+ expect(described_class).to receive(:crypt_password).with(good_password).and_return('crypted_password')
365
+ expect(described_class).to_not receive(:fetch)
366
+
367
+ expect(described_class.create('example.com', 'login', good_password, dry_run: true)).to eq('status' => 'success')
368
+ end
257
369
  end
258
370
 
259
371
  describe '.fetch' do
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe GandiV5::Organization::Customer do
4
+ let(:body_fixtures) { File.expand_path(File.join('spec', 'fixtures', 'bodies', 'GandiV5_Organization_Customer')) }
5
+
6
+ describe '.create' do
7
+ let(:url) { 'https://api.gandi.net/v5/organization/organizations/uuid/customers' }
8
+ let(:attrs) do
9
+ {
10
+ city: 'Ci',
11
+ country: 'Co',
12
+ email: 'a@e',
13
+ firstname: 'f',
14
+ lastname: 'l',
15
+ phone: '0',
16
+ streetaddr: 'sa',
17
+ type: :individual
18
+ }
19
+ end
20
+
21
+ it 'Success' do
22
+ response = double RestClient::Response, headers: { location: '' }
23
+ expect(GandiV5).to receive(:post).with(url, attrs.to_json).and_return([response, nil])
24
+ expect(described_class.create('uuid', **attrs)).to be nil
25
+ end
26
+
27
+ describe 'Checks for required attributes' do
28
+ %i[city country email firstname lastname phone streetaddr type].each do |attr|
29
+ it attr do
30
+ attrs.delete attr
31
+ expect { described_class.new('org_uuid', **attrs) }.to raise_exception ArgumentError
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'Invalid type' do
37
+ attrs[:type] = :invalid
38
+ expect { described_class.new('org_uuid', **attrs) }.to raise_exception ArgumentError
39
+ end
40
+ end
41
+
42
+ describe '.list' do
43
+ describe 'With default values' do
44
+ subject { described_class.list('uuid') }
45
+
46
+ before :each do
47
+ url = 'https://api.gandi.net/v5/organization/organizations/uuid/customers'
48
+ expect(GandiV5).to receive(:get).with(url, params: {})
49
+ .and_return([nil, YAML.load_file(File.join(body_fixtures, 'list.yml'))])
50
+ end
51
+
52
+ its('count') { should eq 1 }
53
+ its('first.uuid') { should eq 'customer-uuid' }
54
+ its('first.name') { should eq 'FirstLast' }
55
+ its('first.first_name') { should eq 'First' }
56
+ its('first.last_name') { should eq 'Last' }
57
+ its('first.email') { should eq 'first.last@example.com' }
58
+ its('first.type') { should eq :individual }
59
+ its('first.org_name') { should eq 'Org' }
60
+ end
61
+
62
+ describe 'Passes optional query params' do
63
+ let(:url) { 'https://api.gandi.net/v5/organization/organizations/org_uuid/customers' }
64
+
65
+ it 'name' do
66
+ expect(GandiV5).to receive(:get).with(url, params: { '~name' => '5' })
67
+ .and_return([nil, []])
68
+ expect(described_class.list('org_uuid', name: '5')).to eq []
69
+ end
70
+
71
+ %i[permission sort_by].each do |param|
72
+ it param.to_s do
73
+ headers = { param => 5 }
74
+ expect(GandiV5).to receive(:get).with(url, params: headers)
75
+ .and_return([nil, []])
76
+ expect(described_class.list('org_uuid', **headers)).to eq []
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -64,4 +64,18 @@ describe GandiV5::Organization do
64
64
  its('security_email_validated') { should eq true }
65
65
  its('security_email_validation_deadline') { should eq Time.new(2017, 11, 22, 17, 13, 33) }
66
66
  end
67
+
68
+ it '#customers' do
69
+ subject = described_class.new uuid: 'org_uuid'
70
+ returns = double Array
71
+ expect(GandiV5::Organization::Customer).to receive(:list).with('org_uuid', param: :value).and_return(returns)
72
+ expect(subject.customers('org_uuid', param: :value)).to be returns
73
+ end
74
+
75
+ it '#create_customer' do
76
+ subject = described_class.new uuid: 'org_uuid'
77
+ returns = double Object
78
+ expect(GandiV5::Organization::Customer).to receive(:create).with('org_uuid', param: :value).and_return(returns)
79
+ expect(subject.create_customer('org_uuid', param: :value)).to be returns
80
+ end
67
81
  end
@@ -47,6 +47,10 @@ describe GandiV5 do
47
47
  end
48
48
 
49
49
  describe 'Uses RestClient' do
50
+ let(:response) do
51
+ double RestClient::Response, body: 'Hello world!', headers: { content_type: 'text/plain' }
52
+ end
53
+
50
54
  %i[get delete].each do |method|
51
55
  describe ":#{method}" do
52
56
  it 'As JSON' do
@@ -58,30 +62,30 @@ describe GandiV5 do
58
62
  end
59
63
 
60
64
  it 'As text' do
61
- response_data = 'Hello world!'
62
- response = double RestClient::Response, body: response_data, headers: { content_type: 'text/plain' }
63
65
  expect(described_class).to receive(:prepare_headers)
64
66
  expect(RestClient).to receive(method).with('url', hash_including(accept: 'text/plain')).and_return(response)
65
- expect(described_class.send(method, 'url', accept: 'text/plain')).to match_array [response, response_data]
67
+ expect(described_class.send(method, 'url', accept: 'text/plain')).to match_array [response, 'Hello world!']
66
68
  end
67
69
 
68
70
  it 'Passes request headers' do
69
71
  expect(described_class).to receive(:prepare_headers)
70
72
  expect(described_class).to receive(:parse_response)
71
- expect(RestClient).to receive(method).with(anything, header: 'value')
72
- expect(described_class.send(method, 'url', header: 'value')).to match_array [nil, nil]
73
+ expect(RestClient).to receive(method).with(anything, header: 'value').and_return(response)
74
+ expect(described_class.send(method, 'url', header: 'value')).to match_array [response, nil]
73
75
  end
74
76
 
75
77
  it 'Adds authentication header' do
76
78
  expect(RestClient).to receive(method).with(anything, hash_including(Authorization: 'Apikey abdce12345'))
79
+ .and_return(response)
77
80
  expect(described_class).to receive(:parse_response)
78
- expect(described_class.send(method, 'https://api.gandi.net/v5/')).to match_array [nil, nil]
81
+ expect(described_class.send(method, 'https://api.gandi.net/v5/')).to match_array [response, nil]
79
82
  end
80
83
 
81
84
  it 'Default accept header' do
82
85
  expect(RestClient).to receive(method).with(any_args, hash_including(accept: 'application/json'))
86
+ .and_return(response)
83
87
  expect(described_class).to receive(:parse_response)
84
- expect(described_class.send(method, 'https://api.gandi.net/v5/')).to match_array [nil, nil]
88
+ expect(described_class.send(method, 'https://api.gandi.net/v5/')).to match_array [response, nil]
85
89
  end
86
90
 
87
91
  it 'Converts a 406 (bad request) exception' do
@@ -92,6 +96,13 @@ describe GandiV5 do
92
96
  end
93
97
  end
94
98
 
99
+ it ':delete handles no content-type' do
100
+ response = double RestClient::Response, headers: {}
101
+ expect(described_class).to receive(:prepare_headers)
102
+ expect(RestClient).to receive(:delete).with('url', hash_including(accept: 'text/plain')).and_return(response)
103
+ expect(described_class.delete('url', accept: 'text/plain')).to match_array [response, nil]
104
+ end
105
+
95
106
  %i[patch post put].each do |method|
96
107
  describe ":#{method}" do
97
108
  let(:payload) { '{"say":"hello world"}' }
@@ -107,26 +118,25 @@ describe GandiV5 do
107
118
  end
108
119
 
109
120
  it 'As text' do
110
- response_data = 'hello world'
111
- response = double RestClient::Response, body: response_data, headers: { content_type: 'text/plain' }
112
121
  expect(described_class).to receive(:prepare_headers)
113
122
  expect(RestClient).to receive(method).with('url', payload, hash_including(accept: 'text/plain'))
114
123
  .and_return(response)
115
- expect(described_class.send(method, 'url', payload, accept: 'text/plain')).to match_array [response, response_data]
124
+ array = [response, 'Hello world!']
125
+ expect(described_class.send(method, 'url', payload, accept: 'text/plain')).to match_array array
116
126
  end
117
127
 
118
128
  it 'Passes payload' do
119
129
  expect(described_class).to receive(:prepare_headers)
120
130
  expect(described_class).to receive(:parse_response)
121
- expect(RestClient).to receive(method).with(anything, payload, any_args)
122
- expect(described_class.send(method, 'url', payload)).to match_array [nil, nil]
131
+ expect(RestClient).to receive(method).with(anything, payload, any_args).and_return(response)
132
+ expect(described_class.send(method, 'url', payload)).to match_array [response, nil]
123
133
  end
124
134
 
125
135
  it 'Passes request headers' do
126
136
  expect(described_class).to receive(:prepare_headers)
127
137
  expect(described_class).to receive(:parse_response)
128
- expect(RestClient).to receive(method).with(any_args, hash_including(header: 'value'))
129
- expect(described_class.send(method, 'url', payload, header: 'value')).to match_array [nil, nil]
138
+ expect(RestClient).to receive(method).with(any_args, hash_including(header: 'value')).and_return(response)
139
+ expect(described_class.send(method, 'url', payload, header: 'value')).to match_array [response, nil]
130
140
  end
131
141
 
132
142
  it 'Adds content type header' do
@@ -137,14 +147,16 @@ describe GandiV5 do
137
147
 
138
148
  it 'Adds authentication header' do
139
149
  expect(RestClient).to receive(method).with(any_args, hash_including(Authorization: 'Apikey abdce12345'))
150
+ .and_return(response)
140
151
  expect(described_class).to receive(:parse_response)
141
- expect(described_class.send(method, 'https://api.gandi.net/v5/', payload)).to match_array [nil, nil]
152
+ expect(described_class.send(method, 'https://api.gandi.net/v5/', payload)).to match_array [response, nil]
142
153
  end
143
154
 
144
155
  it 'Default accept header' do
145
156
  expect(RestClient).to receive(method).with(any_args, hash_including(accept: 'application/json'))
157
+ .and_return(response)
146
158
  expect(described_class).to receive(:parse_response)
147
- expect(described_class.send(method, 'https://api.gandi.net/v5/', payload)).to match_array [nil, nil]
159
+ expect(described_class.send(method, 'https://api.gandi.net/v5/', payload)).to match_array [response, nil]
148
160
  end
149
161
 
150
162
  it 'Converts a 406 (bad request) exception' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gandi_v5
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Gauld
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-03 00:00:00.000000000 Z
11
+ date: 2020-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -340,6 +340,7 @@ files:
340
340
  - lib/gandi_v5/live_dns/zone.rb
341
341
  - lib/gandi_v5/live_dns/zone/snapshot.rb
342
342
  - lib/gandi_v5/organization.rb
343
+ - lib/gandi_v5/organization/customer.rb
343
344
  - lib/gandi_v5/version.rb
344
345
  - spec/.rubocop.yml
345
346
  - spec/features/domain_spec.rb
@@ -371,6 +372,7 @@ files:
371
372
  - spec/fixtures/bodies/GandiV5_LiveDNS_Zone_Snapshot/list.yml
372
373
  - spec/fixtures/bodies/GandiV5_Organization/fetch.yml
373
374
  - spec/fixtures/bodies/GandiV5_Organization/list.yml
375
+ - spec/fixtures/bodies/GandiV5_Organization_Customer/list.yml
374
376
  - spec/fixtures/vcr/Domain_features/List_domains.yml
375
377
  - spec/fixtures/vcr/Domain_features/Renew_domain.yml
376
378
  - spec/fixtures/vcr/LiveDNS_Domain_features/List_domains.yml
@@ -415,6 +417,7 @@ files:
415
417
  - spec/units/gandi_v5/live_dns/zone/snapshot_spec.rb
416
418
  - spec/units/gandi_v5/live_dns/zone_spec.rb
417
419
  - spec/units/gandi_v5/live_dns_spec.rb
420
+ - spec/units/gandi_v5/organization/customer_spec.rb
418
421
  - spec/units/gandi_v5/organization_spec.rb
419
422
  - spec/units/gandi_v5_spec.rb
420
423
  homepage: https://github.com/robertgauld/gandi_v5
@@ -436,7 +439,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
436
439
  - !ruby/object:Gem::Version
437
440
  version: 2.6.14
438
441
  requirements: []
439
- rubygems_version: 3.0.3
442
+ rubygems_version: 3.1.2
440
443
  signing_key:
441
444
  specification_version: 4
442
445
  summary: Make use of Gandi's V5 API.