soapy_cake 1.15.0 → 1.16.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
  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