soapy_cake 1.15.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ded8535f4f057f3e55884f2c0cc218743e4134b9
4
- data.tar.gz: 34dda1abcf277214c5d9442e35da548fc3667b36
3
+ metadata.gz: 27d71b226fedab0e2e0d7da24e6ade998e9468c1
4
+ data.tar.gz: ef9dbece2ee345c0c89dcbb8fa2a49cdcd46092b
5
5
  SHA512:
6
- metadata.gz: a0ffe0fa81c8b12a34b56cc3a680825ccf2dbee32106b76ee7bdb812d154b6b1cb99cbbdcdce4a88dfbbf72d8ebeaf2f9a852e30dc099cbb378a072499bb343f
7
- data.tar.gz: e27a015d263b827d239142cd315a294846c2b8aacd90b5a7edf6e8c0d56e5333b73968c7d47e8bb43fd1ec81e858b993496571a4fd8098e6fc677b0af171e469
6
+ metadata.gz: 49718d7793da7f77650c1df7bb034e76334d3b86c60450fb435f8309d480b9fba2d2be98cfc8e20e20c0912978482a8b41b530789d83f9b3ae732e03a9f52c29
7
+ data.tar.gz: 80f82b060367a774ee40971ab9d10ec7d40135a05cabd6fc4cccc57a08735ee88b49bb1a6b5eb758e1a458a9b2b991f27b45ea5a7a17c791d031ecdff4b476fd
data/README.md CHANGED
@@ -23,7 +23,7 @@ Or install it yourself as:
23
23
 
24
24
  ## Usage Examples
25
25
 
26
- First we assume that you set the `CAKE_DOMAIN`, `CAKE_API_KEY` and `CAKE_TIME_OFFSET`
26
+ First we assume that you set the `CAKE_DOMAIN`, `CAKE_API_KEY` and `CAKE_TIME_ZONE`
27
27
  environment variables.
28
28
 
29
29
  Export all advertisers:
@@ -44,6 +44,12 @@ SoapyCake::Admin.new.affiliate_summary(
44
44
  If you are interested in how we map methods to api endpoints take a look
45
45
  at [api_versions.yml](api_versions.yml).
46
46
 
47
+ ## Time/Date Handling
48
+ - Define `CAKE_TIME_ZONE`
49
+ - Specify dates with any timezone in requests (will be converted to the correct CAKE timezone)
50
+ - `Date` objects in requests will be treated as UTC (just like `.to_datetime` would do)
51
+ - Responses contain UTC dates which can be converted however you like
52
+
47
53
  ## Contributing
48
54
 
49
55
  1. Fork it (https://github.com/ad2games/soapy_cake/fork)
@@ -2,15 +2,14 @@ require 'nokogiri'
2
2
  require 'saxerator'
3
3
  require 'httparty'
4
4
  require 'active_support/core_ext/hash/reverse_merge'
5
- require 'active_support/core_ext/date'
6
- require 'active_support/core_ext/numeric/time'
7
- require 'active_support/core_ext/time/zones'
8
5
  require 'active_support/core_ext/hash/transform_values'
6
+ require 'active_support/time'
9
7
 
10
8
  require 'soapy_cake/version'
11
9
  require 'soapy_cake/const'
12
10
  require 'soapy_cake/helper'
13
11
  require 'soapy_cake/error'
12
+ require 'soapy_cake/time_converter'
14
13
  require 'soapy_cake/request'
15
14
  require 'soapy_cake/response'
16
15
  require 'soapy_cake/response_value'
@@ -1,12 +1,15 @@
1
1
  module SoapyCake
2
2
  class Client
3
- attr_reader :domain, :api_key, :time_offset
3
+ attr_reader :domain, :api_key, :time_converter
4
4
 
5
5
  def initialize(opts = {})
6
6
  @domain = opts.fetch(:domain, ENV['CAKE_DOMAIN']) || fail(Error, 'Cake domain missing')
7
7
  @api_key = opts.fetch(:api_key, ENV['CAKE_API_KEY']) || fail(Error, 'Cake API key missing')
8
- @time_offset = opts.fetch(:time_offset, ENV['CAKE_TIME_OFFSET']) ||
9
- fail(Error, 'Cake time offset missing')
8
+
9
+ time_offset = opts.fetch(:time_offset, ENV['CAKE_TIME_OFFSET'])
10
+ time_zone = opts.fetch(:time_zone, ENV['CAKE_TIME_ZONE'])
11
+ @time_converter = TimeConverter.new(time_zone, time_offset)
12
+
10
13
  @opts = opts
11
14
  end
12
15
 
@@ -20,11 +23,10 @@ module SoapyCake
20
23
 
21
24
  def run(request)
22
25
  request.api_key = api_key
23
- request.time_offset = time_offset
26
+ request.time_converter = time_converter
24
27
 
25
- Response
26
- .new(response_body(request), request.short_response?, time_offset)
27
- .public_send(:"to_#{xml_response? ? 'xml' : 'enum'}")
28
+ response = Response.new(response_body(request), request.short_response?, time_converter)
29
+ xml_response? ? response.to_xml : response.to_enum
28
30
  end
29
31
 
30
32
  private
@@ -1,6 +1,8 @@
1
1
  module SoapyCake
2
2
  class Request
3
- attr_accessor :api_key, :time_offset
3
+ DATE_CLASSES = [Date, Time, DateTime].freeze
4
+
5
+ attr_accessor :api_key, :time_converter
4
6
  attr_reader :role, :service, :method, :opts
5
7
 
6
8
  def initialize(role, service, method, opts = {})
@@ -40,7 +42,7 @@ module SoapyCake
40
42
  def xml_params(xml)
41
43
  xml.api_key api_key
42
44
  opts.each do |k, v|
43
- xml.public_send(k.to_sym, format_param(v))
45
+ xml.public_send(k.to_sym, format_param(k, v))
44
46
  end
45
47
  end
46
48
 
@@ -51,13 +53,14 @@ module SoapyCake
51
53
  }
52
54
  end
53
55
 
54
- def format_param(value)
55
- case value
56
- when Time, DateTime, Date
57
- (value.to_datetime.utc + time_offset.to_i.hours).strftime('%Y-%m-%dT%H:%M:%S')
58
- else
59
- value
56
+ def format_param(key, value)
57
+ return time_converter.to_cake(value) if DATE_CLASSES.include?(value.class)
58
+
59
+ if key.to_s.end_with?('_date')
60
+ fail Error, "You need to use a Time/DateTime/Date object for '#{key}'"
60
61
  end
62
+
63
+ value
61
64
  end
62
65
 
63
66
  def version
@@ -5,12 +5,12 @@ module SoapyCake
5
5
  SHORT_ELEMENT_DEPTH = 3
6
6
  ELEMENTS_DEPTH = 5
7
7
 
8
- attr_reader :body, :short_response, :time_offset
8
+ attr_reader :body, :short_response, :time_converter
9
9
 
10
- def initialize(body, short_response, time_offset)
10
+ def initialize(body, short_response, time_converter)
11
11
  @body = body
12
12
  @short_response = short_response
13
- @time_offset = time_offset
13
+ @time_converter = time_converter
14
14
  end
15
15
 
16
16
  def to_enum
@@ -37,7 +37,7 @@ module SoapyCake
37
37
 
38
38
  def typed_element(element)
39
39
  walk_tree(element) do |value, key|
40
- ResponseValue.new(key, value, time_offset: time_offset).parse
40
+ ResponseValue.new(key, value, time_converter).parse
41
41
  end
42
42
  end
43
43
 
@@ -6,17 +6,17 @@ module SoapyCake
6
6
  # Known string ids that should not be parsed as integers
7
7
  STRING_IDS = %w(tax_id transaction_id).freeze
8
8
 
9
- def initialize(key, value, opts = {})
9
+ def initialize(key, value, time_converter)
10
10
  @key = key.to_s
11
11
  @value = value
12
- @opts = opts
12
+ @time_converter = time_converter
13
13
  end
14
14
 
15
15
  def parse
16
16
  return parse_int if id? && !string_id?
17
17
  return false if false?
18
18
  return true if true?
19
- return parse_date if date?
19
+ return time_converter.from_cake(value) if date?
20
20
 
21
21
  # cast to primitive string to get rid of Saxerator string class
22
22
  value.to_s
@@ -24,6 +24,8 @@ module SoapyCake
24
24
 
25
25
  private
26
26
 
27
+ attr_reader :time_converter
28
+
27
29
  def false?
28
30
  value == 'false'
29
31
  end
@@ -54,9 +56,5 @@ module SoapyCake
54
56
  end
55
57
  value.to_i
56
58
  end
57
-
58
- def parse_date
59
- DateTime.parse(value + format('%+03d:00', @opts[:time_offset].to_i))
60
- end
61
59
  end
62
60
  end
@@ -0,0 +1,35 @@
1
+ module SoapyCake
2
+ class TimeConverter
3
+ def initialize(time_zone, time_offset = nil)
4
+ if time_offset
5
+ self.class.print_deprecation_warning
6
+
7
+ # Etc/GMT time zones have their sign reversed
8
+ time_zone = format('Etc/GMT%+d', -time_offset.to_i)
9
+ end
10
+
11
+ fail Error, 'Cake time zone missing' if time_zone.blank?
12
+ @zone = ActiveSupport::TimeZone[time_zone]
13
+ end
14
+
15
+ def to_cake(date)
16
+ date = date.to_datetime if date.is_a?(Date)
17
+ date.in_time_zone(zone).strftime('%Y-%m-%dT%H:%M:%S')
18
+ end
19
+
20
+ def from_cake(value)
21
+ zone.parse(value).utc
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :zone
27
+
28
+ def self.print_deprecation_warning
29
+ return if @deprecation_warning_printed
30
+ @deprecation_warning_printed = true
31
+
32
+ STDERR.puts 'SoapyCake - DEPRECATED: Please use time_zone instead of time_offset.'
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module SoapyCake
2
- VERSION = '1.15.0'.freeze
2
+ VERSION = '1.16.0'.freeze
3
3
  end
@@ -26,7 +26,7 @@ http_interactions:
26
26
  <cake:contact_first_name>Foxy</cake:contact_first_name>
27
27
  <cake:contact_last_name>Fox</cake:contact_last_name>
28
28
  <cake:contact_email_address>foxy@forrest.com</cake:contact_email_address>
29
- <cake:date_added>2015-06-15T01:00:00</cake:date_added>
29
+ <cake:date_added>2015-06-15T02:00:00</cake:date_added>
30
30
  </cake:Affiliate>
31
31
  </env:Body>
32
32
  </env:Envelope>
@@ -12,8 +12,8 @@ http_interactions:
12
12
  <env:Body>
13
13
  <cake:Conversions>
14
14
  <cake:api_key>cake-api-key</cake:api_key>
15
- <cake:start_date>2015-04-11T01:00:00</cake:start_date>
16
- <cake:end_date>2015-04-12T01:00:00</cake:end_date>
15
+ <cake:start_date>2015-04-11T02:00:00</cake:start_date>
16
+ <cake:end_date>2015-04-12T02:00:00</cake:end_date>
17
17
  <cake:row_limit>1</cake:row_limit>
18
18
  <cake:conversion_type>conversions</cake:conversion_type>
19
19
  </cake:Conversions>
@@ -12,8 +12,8 @@ http_interactions:
12
12
  <env:Body>
13
13
  <cake:Clicks>
14
14
  <cake:api_key>cake-api-key</cake:api_key>
15
- <cake:start_date>2014-06-30T01:00:00</cake:start_date>
16
- <cake:end_date>2014-07-01T01:00:00</cake:end_date>
15
+ <cake:start_date>2014-06-30T02:00:00</cake:start_date>
16
+ <cake:end_date>2014-07-01T02:00:00</cake:end_date>
17
17
  <cake:row_limit>1</cake:row_limit>
18
18
  </cake:Clicks>
19
19
  </env:Body>
@@ -12,7 +12,7 @@ http_interactions:
12
12
  <env:Body>
13
13
  <cake:MassConversionInsert>
14
14
  <cake:api_key>cake-api-key</cake:api_key>
15
- <cake:conversion_date>2015-05-06T01:00:00</cake:conversion_date>
15
+ <cake:conversion_date>2015-05-06T02:00:00</cake:conversion_date>
16
16
  <cake:affiliate_id>16059</cake:affiliate_id>
17
17
  <cake:campaign_id>13268</cake:campaign_id>
18
18
  <cake:sub_affiliate/>
@@ -12,12 +12,15 @@ RSpec.describe SoapyCake::Admin do
12
12
  hide_offers: false,
13
13
  # hashes and id-params
14
14
  billing_cycle: { billing_cycle_id: 1, billing_cycle_name: 'Weekly' },
15
- # dates
16
- date_created: DateTime.new(2014, 4, 28, 10, 52, 15.537),
17
15
  # floats
18
16
  minimum_payment_threshold: '0.0000'
19
17
  )
20
18
 
19
+ # dates
20
+ expect(result.first[:date_created]).to be_a(Time)
21
+ expect(result.first[:date_created].to_s)
22
+ .to eq(Time.utc(2014, 4, 28, 8, 52, 15.537).to_s)
23
+
21
24
  # strings should be actual Strings, not some Saxerator element class
22
25
  expect(result.first[:affiliate_name].class).to eq(String)
23
26
 
@@ -4,4 +4,10 @@ RSpec.describe SoapyCake::Request do
4
4
  described_class.new(:test, :does, :not_exist).xml
5
5
  end.to raise_error(SoapyCake::Error, 'Unknown API call test::does::not_exist')
6
6
  end
7
+
8
+ it 'raises when you pass non-date-like objects in date fields' do
9
+ expect do
10
+ described_class.new(:admin, :export, :offers, start_date: '2015-01-01').xml
11
+ end.to raise_error(SoapyCake::Error, /Date object for 'start_date'/)
12
+ end
7
13
  end
@@ -1,58 +1,55 @@
1
1
  RSpec.describe SoapyCake::ResponseValue do
2
- def subject(*args)
3
- described_class.new(*args)
2
+ let(:time_converter) { double('date converter') }
3
+
4
+ def parse(key, value)
5
+ described_class.new(key, value, time_converter).parse
4
6
  end
5
7
 
6
8
  describe '#parse' do
7
9
  context 'booleans' do
8
10
  it 'converts the string "true" to the true value' do
9
- expect(subject(:foo, 'true').parse).to eq(true)
11
+ expect(parse(:foo, 'true')).to eq(true)
10
12
  end
11
13
 
12
14
  it 'converts the string "false" to the false value' do
13
- expect(subject(:foo, 'false').parse).to eq(false)
15
+ expect(parse(:foo, 'false')).to eq(false)
14
16
  end
15
17
  end
16
18
 
17
19
  context 'dates' do
18
- it 'parses an ISO-formatted date' do
19
- expect(subject(:foo, '2014-06-30T01:00:00').parse).to eq(DateTime.new(2014, 6, 30, 1))
20
- end
21
-
22
- it 'properly applies the provided time offset' do
23
- expect(subject(:foo, '2014-01-01T00:00:00', time_offset: 5).parse)
24
- .to eq(DateTime.new(2014, 1, 1, 0).change(offset: '+0500'))
20
+ it 'delegates date-conversion to a TimeConverter instance' do
21
+ expect(time_converter).to receive(:from_cake)
22
+ .with('2014-06-30T01:00:00').and_return(:nice_date)
25
23
 
26
- expect(subject(:foo, '2014-12-31T01:00:00', time_offset: -2).parse)
27
- .to eq(DateTime.new(2014, 12, 31, 1).change(offset: '-0200'))
24
+ expect(parse(:foo, '2014-06-30T01:00:00')).to eq(:nice_date)
28
25
  end
29
26
  end
30
27
 
31
28
  context 'strings' do
32
29
  it 'parses a string' do
33
- expect(subject(:foo, 'abc').parse).to eq('abc')
30
+ expect(parse(:foo, 'abc')).to eq('abc')
34
31
  end
35
32
  end
36
33
 
37
34
  context 'IDs' do
38
35
  it 'converts keys ending in "_id" to integers' do
39
- expect(subject(:conversion_id, '42').parse).to eq(42)
36
+ expect(parse(:conversion_id, '42')).to eq(42)
40
37
  end
41
38
 
42
39
  it "doesn't convert ids in the blacklist to integers" do
43
- expect(subject(:tax_id, '123abc').parse).to eq('123abc')
44
- expect(subject(:other_tax_id, '123abc').parse).to eq('123abc')
45
- expect(subject(:transaction_id, '123abc').parse).to eq('123abc')
40
+ expect(parse(:tax_id, '123abc')).to eq('123abc')
41
+ expect(parse(:other_tax_id, '123abc')).to eq('123abc')
42
+ expect(parse(:transaction_id, '123abc')).to eq('123abc')
46
43
  end
47
44
 
48
45
  it 'raises an error if not in the blacklist and non-digit characters are detected' do
49
- expect { subject(:foo_id, '123abc').parse }.to raise_error(SoapyCake::Error)
46
+ expect { parse(:foo_id, '123abc') }.to raise_error(SoapyCake::Error)
50
47
  end
51
48
 
52
49
  it 'does not raise an error if the value is absent' do
53
- expect(subject(:foo_id, '-1').parse).to eq(-1)
54
- expect(subject(:foo_id, '').parse).to eq(0)
55
- expect(subject(:foo_id, nil).parse).to eq(0)
50
+ expect(parse(:foo_id, '-1')).to eq(-1)
51
+ expect(parse(:foo_id, '')).to eq(0)
52
+ expect(parse(:foo_id, nil)).to eq(0)
56
53
  end
57
54
  end
58
55
  end
@@ -0,0 +1,37 @@
1
+ RSpec.describe SoapyCake::TimeConverter do
2
+ subject { described_class.new('Europe/Berlin') }
3
+
4
+ describe '#to_cake' do
5
+ it 'converts a time-like object into a cake timestamp' do
6
+ expect(subject.to_cake(DateTime.new(2015, 1, 2, 12, 30))).to eq('2015-01-02T13:30:00')
7
+ expect(subject.to_cake(Time.utc(2015, 1, 2, 12, 30))).to eq('2015-01-02T13:30:00')
8
+ expect(subject.to_cake(Date.new(2015, 1, 2))).to eq('2015-01-02T01:00:00')
9
+ end
10
+
11
+ it 'respects DST' do
12
+ expect(subject.to_cake(Time.utc(2015, 1, 2, 12, 30))).to eq('2015-01-02T13:30:00')
13
+ expect(subject.to_cake(Time.utc(2015, 6, 2, 12, 30))).to eq('2015-06-02T14:30:00')
14
+ end
15
+ end
16
+
17
+ describe '#from_cake' do
18
+ it 'parses cake dates into the specified time zone (including DST)' do
19
+ expect(subject.from_cake('2015-01-11T14:53:40.000')).to be_a(Time)
20
+ expect(subject.from_cake('2015-01-11T14:53:40.000'))
21
+ .to eq(Time.utc(2015, 1, 11, 13, 53, 40))
22
+ expect(subject.from_cake('2015-06-11T14:53:40.000'))
23
+ .to eq(Time.utc(2015, 6, 11, 12, 53, 40))
24
+ end
25
+ end
26
+
27
+ context 'legacy mode / CAKE_TIME_OFFSET' do
28
+ subject { described_class.new('Europe/Berlin', 5) }
29
+
30
+ it 'works as before (broken, without DST)' do
31
+ expect(STDERR).to receive(:puts).with(/Please use time_zone/)
32
+
33
+ expect(subject.to_cake(DateTime.new(2015, 1, 2, 12, 30))).to eq('2015-01-02T17:30:00')
34
+ expect(subject.to_cake(DateTime.new(2015, 6, 2, 12, 30))).to eq('2015-06-02T17:30:00')
35
+ end
36
+ end
37
+ end
@@ -41,7 +41,7 @@ end
41
41
 
42
42
  ENV['CAKE_API_KEY'] = 'cake-api-key' if ENV['CAKE_API_KEY'].blank?
43
43
  ENV['CAKE_DOMAIN'] = 'cake-partner-domain.com' if ENV['CAKE_DOMAIN'].blank?
44
- ENV['CAKE_TIME_OFFSET'] = '1' if ENV['CAKE_TIME_OFFSET'].blank?
44
+ ENV['CAKE_TIME_ZONE'] = 'Europe/Berlin' if ENV['CAKE_TIME_ZONE'].blank?
45
45
 
46
46
  VCR.configure do |c|
47
47
  c.configure_rspec_metadata!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soapy_cake
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.0
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ad2games GmbH
@@ -193,6 +193,7 @@ files:
193
193
  - lib/soapy_cake/request.rb
194
194
  - lib/soapy_cake/response.rb
195
195
  - lib/soapy_cake/response_value.rb
196
+ - lib/soapy_cake/time_converter.rb
196
197
  - lib/soapy_cake/version.rb
197
198
  - soapy_cake.gemspec
198
199
  - spec/fixtures/raw_response.xml
@@ -229,6 +230,7 @@ files:
229
230
  - spec/lib/soapy_cake/request_spec.rb
230
231
  - spec/lib/soapy_cake/response_spec.rb
231
232
  - spec/lib/soapy_cake/response_value_spec.rb
233
+ - spec/lib/soapy_cake/time_converter_spec.rb
232
234
  - spec/spec_helper.rb
233
235
  - spec/support/admin_method_example.rb
234
236
  homepage: http://ad2games.com
@@ -251,7 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
251
253
  version: '0'
252
254
  requirements: []
253
255
  rubyforge_project:
254
- rubygems_version: 2.4.5.1
256
+ rubygems_version: 2.4.8
255
257
  signing_key:
256
258
  specification_version: 4
257
259
  summary: Simple client for the CAKE API
@@ -290,5 +292,6 @@ test_files:
290
292
  - spec/lib/soapy_cake/request_spec.rb
291
293
  - spec/lib/soapy_cake/response_spec.rb
292
294
  - spec/lib/soapy_cake/response_value_spec.rb
295
+ - spec/lib/soapy_cake/time_converter_spec.rb
293
296
  - spec/spec_helper.rb
294
297
  - spec/support/admin_method_example.rb