promonizr 1.13.2

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.
@@ -0,0 +1,48 @@
1
+ module Promonizr
2
+ module Products
3
+
4
+ TYPE_CODES = %w[car_insurance travel_assistance]
5
+ API_ENDPOINTS = %w[attributes advanced]
6
+
7
+ module_function
8
+
9
+ def api_url
10
+ CoConfig::PROMOTION_API[:url] + '/products'
11
+ end
12
+
13
+ # Returns the url associated to the given subresource using
14
+ # as prefix the value returned by <tt>Products.api_url</tt>
15
+ #
16
+ # ==== Examples
17
+ # attributes_url('car_insurance')
18
+ # #=> "…/products/car_insurance/attributes"
19
+ #
20
+ # advanced_url('travel_assistance')
21
+ # #=> "…/products/travel_assistance/advanced"
22
+ API_ENDPOINTS.each do |url_segment|
23
+ define_method("#{url_segment}_url"){ |id| "#{api_url}/#{id}/#{url_segment}" }
24
+ end
25
+
26
+ # Returns the attributes that can be used as variables on the rules as.
27
+ # The expected response from the service is an array, if it is
28
+ # anything else, the response will be an emtpy array.
29
+ #
30
+ # API reference:
31
+ # https://github.com/comparaonline/promotions/wiki/API-Products#attributes
32
+ # https://github.com/comparaonline/promotions/wiki/API-Products#advanced
33
+ #
34
+ # ==== Examples
35
+ # attributes('car_insurance', 'cl') # => []
36
+ # advanced('travel_assistance', 'br') # =>
37
+ API_ENDPOINTS.each do |rule|
38
+ define_method(rule) do |id, country_code|
39
+ url = public_send("#{rule}_url", id)
40
+ params = {countryCode: country_code}
41
+ response = Request.execute(:get, url, params)
42
+
43
+ response.is_a?(Array) ? response : []
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,63 @@
1
+ require 'ostruct'
2
+ require 'uri'
3
+
4
+ module Promonizr
5
+ class Promotion < OpenStruct
6
+ class Nil
7
+ def show_promotion?; false; end
8
+ def kind; nil; end
9
+ def title; nil; end
10
+ def subtitle; nil; end
11
+ def terms_of_service; nil; end
12
+ def start_date; nil; end
13
+ def end_date; nil; end
14
+ def companies; []; end
15
+ end
16
+
17
+ def show_promotion?; true; end
18
+
19
+ def companies
20
+ unless names = @table[:companies]
21
+ Promonizr::Companies.all(country_code, product)
22
+ else
23
+ Promonizr::Companies.by_url_names(names, country_code, product)
24
+ end
25
+ end
26
+
27
+ def featured?
28
+ return false if display_config.blank?
29
+ display_config['featured'] == true
30
+ end
31
+
32
+ def show?(url, stage)
33
+ return true if display_config.blank?
34
+
35
+ stage_match = display_config['stage'].try(:[], stage)
36
+ url_excluded = display_config.fetch('url_exclusions', []).any? do |excluded_url|
37
+ url.include?(excluded_url)
38
+ end if url
39
+
40
+ stage_match && !url_excluded
41
+ end
42
+
43
+ def image_url
44
+ s3_to_cloudfront(@table[:image_url])
45
+ end
46
+
47
+ def featured_image_url
48
+ s3_to_cloudfront(@table[:featured_image_url])
49
+ end
50
+
51
+ def as_json(options = nil)
52
+ @table.as_json(options)
53
+ end
54
+
55
+ private
56
+
57
+ def s3_to_cloudfront(url)
58
+ return url if CoConfig::PROMOTION_API[:cdn].empty?
59
+ uri = URI.parse(url)
60
+ URI.join(CoConfig::PROMOTION_API[:cdn], File.basename(uri.path)).to_s
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ module Promonizr
2
+ class Railtie < ::Rails::Railtie
3
+ config.before_configuration do
4
+ CoConfig.load(Promonizr::Engine.root.join('config'))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ module Promonizr
2
+ module Request
3
+ TIMEOUT_SECONDS = 1
4
+ DEFAULT_ANSWER = []
5
+
6
+ module_function
7
+
8
+ def execute(method, url, params = {}, headers = {})
9
+ answer = DEFAULT_ANSWER
10
+
11
+ begin
12
+ Timeout::timeout(TIMEOUT_SECONDS) do
13
+ return false unless [:post, :put, :delete, :get].include?(method)
14
+ response = RestClient::Request.execute(
15
+ method: method,
16
+ url: url,
17
+ payload: method == :get ? {} : params,
18
+ headers: method == :get ? headers.merge(params: params) : headers
19
+ )
20
+ answer = JSON.parse(response)
21
+ end
22
+ rescue Timeout::Error
23
+ Rails.logger.error "Promotions hit timeout on request: #{url} with params:#{params.inspect}"
24
+ rescue StandardError => exception
25
+ Rails.logger.error exception.message + "\n" +
26
+ exception.backtrace.join("\n")
27
+ end
28
+
29
+ return answer
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,54 @@
1
+ module Promonizr
2
+ module Search
3
+ module_function
4
+
5
+ def featured(product, country_code, url, attributes = {})
6
+ config = { url: url, stage: 'landing' }
7
+ all = filter(product, country_code, config, attributes)
8
+
9
+ return nil if all.empty?
10
+ all.detect(&:featured?) || all.first
11
+ end
12
+
13
+ def for_carousel(product, country_code, url, attributes = {})
14
+ config = { url: url, stage: 'landing' }
15
+ filter(product, country_code, config, attributes)
16
+ end
17
+
18
+ def for_results(product, country_code, attributes = {})
19
+ config = { stage: 'results' }
20
+ filter(product, country_code, config, attributes)
21
+ end
22
+
23
+ def for_results_in_bulk(url, enabled, form_data, offers_data)
24
+ default_hash = Hash.new(Promotion::Nil.new)
25
+
26
+ return default_hash unless enabled
27
+
28
+ config = { stage: 'results' }
29
+ results = Hash.new(Promotion::Nil.new)
30
+ Evaluations.evaluate_in_bulk(url, form_data, offers_data)
31
+ .each { |offer_id, list| results[offer_id] = filter_first_campaign(list, config) }
32
+ results
33
+ end
34
+
35
+ def filter(product, country_code, config = {}, attributes = {})
36
+ list = Evaluations.evaluate(product, country_code, attributes)
37
+ filter_campaigns(list, config)
38
+ end
39
+
40
+ def filter_campaigns(list, config)
41
+ list.select!{ |p| p.show?(config[:url], config[:stage]) } if config
42
+ list.sort do |p1, p2|
43
+ p1_rules_size = p1.rules ? p1.rules.keys.size : 0
44
+ p2_rules_size = p2.rules ? p2.rules.keys.size : 0
45
+
46
+ [p1_rules_size, p1.end_date.to_date] <=> [p2_rules_size, p2.end_date.to_date]
47
+ end
48
+ end
49
+
50
+ def filter_first_campaign(list, config)
51
+ filter_campaigns(list, config).first || Promotion::Nil.new
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ module Promonizr
2
+ # Campaigns implements CRUD methods on the Campaign resource from the
3
+ # Promotions web service.
4
+ module SpecialEvents
5
+ JSON_ATTRIBUTES = %i(images).freeze
6
+
7
+ module_function
8
+
9
+ def api_url
10
+ CoConfig::PROMOTION_API[:url] + '/special-events'
11
+ end
12
+
13
+ def all
14
+ @special_events = Request.execute(:get, api_url)
15
+ @special_events.is_a?(Array) ?@special_events : []
16
+ end
17
+
18
+ def create(params)
19
+ Request.execute(:post, api_url, sanitized_params(params))
20
+ end
21
+
22
+ def update(id, params)
23
+ Request.execute(:put, url_with_id(id), sanitized_params(params))
24
+ end
25
+
26
+ def destroy(id)
27
+ Request.execute(:delete, url_with_id(id))
28
+ end
29
+
30
+ def get(id)
31
+ Request.execute(:get, url_with_id(id))
32
+ end
33
+
34
+ def url_with_id(id)
35
+ "#{api_url}/#{id}"
36
+ end
37
+
38
+ def search(params)
39
+ Request.execute(:post, url_with_id('search'), sanitized_params(params))
40
+ end
41
+
42
+ def find_current(params)
43
+ events = search(params)
44
+ events.empty? ? nil : events[0]
45
+ end
46
+
47
+ # Ensures that columns defined in JSON_ATTRIBUTES are json strings.
48
+ # boolean values will be casted
49
+ def sanitized_params(params)
50
+ JSON_ATTRIBUTES.each do |json_column|
51
+ next unless params.key?(json_column)
52
+
53
+ if params[json_column].is_a?(String)
54
+ params[json_column] = JSON.parse(params[json_column])
55
+ end
56
+
57
+ params[json_column] = params[json_column].to_json
58
+ end
59
+
60
+ params
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,47 @@
1
+ module Promonizr
2
+ module Utils
3
+ module_function
4
+
5
+ # Based on the Rails 4.2.1 implementation
6
+ # http://apidock.com/rails/v4.2.1/Hash/transform_values
7
+ def transform_values(hash, &block)
8
+ return yield(hash) unless hash.respond_to?(:each)
9
+ return hash.map{|v| transform_values(v, &block) } if hash.is_a?(Array)
10
+
11
+ result = {}
12
+ hash.each do |key, value|
13
+ if value.is_a?(Hash)
14
+ result[key] = transform_values(value, &block)
15
+ elsif value.is_a?(Array)
16
+ result[key] = value.map{ |v| transform_values(v, &block) }
17
+ else
18
+ result[key] = yield(value)
19
+ end
20
+ end
21
+ result
22
+ end
23
+
24
+ # Checks in the given hash if there are boolean or numeric values, cast them
25
+ # to the proper type.
26
+ def cast_values(hash)
27
+ transform_values(hash) do |value|
28
+ if value.is_a?(String)
29
+ # Is boolean ?
30
+ value = case value.strip.downcase
31
+ when 'true' then true
32
+ when 'false' then false
33
+ else value
34
+ end
35
+ # if not boolean and can be cast to number
36
+ if (!!value != value) && (Float(value) != nil rescue false)
37
+ value = case value.strip.to_i.to_s
38
+ when value then value.to_i
39
+ else value.to_f
40
+ end
41
+ end
42
+ end
43
+ value
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Promonizr
2
+ VERSION = '1.13.2'
3
+ end
data/lib/promonizr.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "promonizr/version"
2
+ require "promonizr/engine"
3
+ require 'promonizr/railtie'
4
+ require 'promonizr/utils'
5
+ require 'promonizr/request'
6
+ require 'promonizr/special_events'
7
+ require 'promonizr/contests'
8
+ require 'promonizr/campaigns'
9
+ require 'promonizr/products'
10
+ require 'promonizr/search'
11
+ require 'promonizr/promotion'
12
+ require 'promonizr/evaluations'
13
+
14
+ module Promonizr
15
+ end
@@ -0,0 +1,29 @@
1
+ require 'factory_girl'
2
+ require 'faker'
3
+
4
+ FactoryGirl.define do
5
+ factory :promotion, class: Promonizr::Promotion do
6
+ product Promonizr::Products::TYPE_CODES.sample
7
+ country_code Promonizr::Campaigns::COUNTRY_CODES.sample
8
+ title Faker::Lorem.sentence
9
+ subtitle Faker::Lorem.sentence
10
+ kind Promonizr::Campaigns::KIND_CODES.sample
11
+ start_date Faker::Date.backward
12
+ end_date Faker::Date.forward
13
+ image_url Faker::Placeholdit.image("350x219")
14
+ featured_image_url Faker::Placeholdit.image("350x219")
15
+ from_service [true, false].sample
16
+ terms_of_service Faker::Lorem.paragraph
17
+ rules { { } }
18
+ benefits { { } }
19
+
20
+ initialize_with { new(attributes) }
21
+
22
+ factory :featured_promotion do
23
+ display_config {{
24
+ 'featured' => true,
25
+ 'stage' => {'landing' => true}
26
+ }}
27
+ end
28
+ end
29
+ end
data/spec/i18n_spec.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require 'i18n/tasks'
3
+
4
+ describe 'I18n' do
5
+ let(:i18n) { I18n::Tasks::BaseTask.new }
6
+ let(:missing_keys) { i18n.missing_keys }
7
+
8
+ it 'does not have missing keys' do
9
+ expect(missing_keys).to be_empty,
10
+ "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
11
+ end
12
+
13
+ end
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ describe Promonizr::Campaigns do
4
+ describe '.api_url' do
5
+ expected_url = CoConfig::PROMOTION_API[:url] + '/campaigns'
6
+ subject(:promonizr_campaigns) {Promonizr::Campaigns}
7
+ it {expect(promonizr_campaigns.api_url).to eq(expected_url)}
8
+ end
9
+
10
+ describe '.url_with_id' do
11
+ expected_url = CoConfig::PROMOTION_API[:url] + '/campaigns/19'
12
+ subject(:promonizr_campaigns) {Promonizr::Campaigns}
13
+ it {expect(promonizr_campaigns.url_with_id('19')).to eq(expected_url)}
14
+ end
15
+
16
+ describe '.sanitized_params' do
17
+ context 'when the params already includes a JSON' do
18
+ subject(:params) { { rules: { a: 1, b: "2" }.to_json } }
19
+ expected_params = { rules: { a: 1, b: 2 }.to_json }
20
+ it do
21
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(expected_params)
22
+ end
23
+ end
24
+
25
+ context 'when params includes a hash' do
26
+ subject(:params) { {rules: {a: 1, b: "2"}, conditions:{}}}
27
+ it do
28
+ expected_params = params.merge({
29
+ rules: params[:rules].merge(b: 2).to_json,
30
+ conditions: params[:conditions].to_json
31
+ })
32
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(expected_params)
33
+ end
34
+ end
35
+
36
+ context 'when params does not include a Campaigns JSON column' do
37
+ subject(:params) { { title: { a: 1, b: "2" } } }
38
+ it do
39
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(params)
40
+ end
41
+ end
42
+
43
+ context 'when params include a Campaigns JSON column with truthy or falsy values' do
44
+ subject(:params) { { display_config: { sales: 'true', results: 'false' } } }
45
+
46
+ it do
47
+ expected_params = {
48
+ display_config: { sales: true, results: false }.to_json
49
+ }
50
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(expected_params)
51
+ end
52
+ end
53
+
54
+ context 'when params have truthy or falsy values, but not in a Campaigns JSON column' do
55
+ subject(:params) { { title: 'true' } }
56
+
57
+ it do
58
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(params)
59
+ end
60
+ end
61
+
62
+ context 'when params include rules with truthy or falsy values' do
63
+ subject(:params) do
64
+ {
65
+ rules: {
66
+ multitravel: {
67
+ conditions: [ { joinOperator: 'and', eq: 'true' } ],
68
+ type: 'simple',
69
+ joinOperator: 'and'
70
+ }
71
+ }
72
+ }
73
+ end
74
+
75
+ it do
76
+ output = JSON.parse(Promonizr::Campaigns.sanitized_params(params)[:rules])
77
+ expect(output['multitravel']['conditions'][0]['eq']).to be true
78
+ end
79
+ end
80
+
81
+ context 'params has an array of strings' do
82
+ subject(:params) do
83
+ {
84
+ display_config: {
85
+ 'featured' => 'true',
86
+ 'url_exclusions' => ['car', 'plane', 'credit'],
87
+ 'stage' => {'landing'=>'false', 'results'=>'true', 'sale'=>'false'}
88
+ }
89
+ }
90
+ end
91
+
92
+ expected_params = {
93
+ display_config: {
94
+ 'featured' => true,
95
+ 'url_exclusions' => ['car', 'plane', 'credit'],
96
+ 'stage' => {'landing'=>false, 'results'=>true, 'sale'=>false}
97
+ }.to_json
98
+ }
99
+
100
+ it do
101
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(expected_params)
102
+ end
103
+ end
104
+
105
+ context 'companies params is a array with mixed values' do
106
+ subject(:params) do
107
+ { companies: ['true', '1', 6, false, { key: {value: 'false'} }]}
108
+ end
109
+
110
+ expected_params = {
111
+ companies: [true, 1, 6, false, { key: { value: false } } ].to_json
112
+ }
113
+ it do
114
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(expected_params)
115
+ end
116
+ end
117
+
118
+ context 'companies params is a array with hashes' do
119
+ subject(:params) do
120
+ { companies: [{}, {}, {}, {}] }
121
+ end
122
+
123
+ expected_params = { companies: [{}, {}, {}, {}].to_json }
124
+
125
+ it do
126
+ expect(Promonizr::Campaigns.sanitized_params(params)).to eq(expected_params)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Promonizr::Evaluations do
4
+ context '#evaluate' do
5
+ it 'returns all promotions for the proper country and product when no attributes are passed' do
6
+ country_code = 'cl'
7
+ product = 'car_insurance'
8
+ default_company_param = nil
9
+
10
+ stub_request(:post, Promonizr::Evaluations::api_url('evaluate'))
11
+ .with(
12
+ body: {
13
+ countryCode: country_code,
14
+ product: product,
15
+ form: {},
16
+ companyName: default_company_param
17
+ }.to_json
18
+ ).to_return(status: 200, body: [
19
+ build(:promotion, { country_code: 'cl', product: 'car_insurance' })
20
+ ].to_json)
21
+
22
+ result = Promonizr::Evaluations.evaluate(product, country_code)
23
+
24
+ expect(result).to_not be_nil
25
+ expect(result).to_not be_empty
26
+ end
27
+
28
+ it 'returns filtered promotions based on attributes' do
29
+ country_code = 'cl'
30
+ product = 'car_insurance'
31
+ company_name = 'some_company'
32
+ attributes = { 'age' => 20, 'gender' => 'male', 'company_name' => company_name }
33
+
34
+ stub_request(:post, Promonizr::Evaluations::api_url('evaluate'))
35
+ .with(
36
+ body: {
37
+ countryCode: country_code,
38
+ product: product,
39
+ form: attributes,
40
+ companyName: company_name
41
+ }.to_json
42
+ ).to_return(status: 200, body: [
43
+ build(:promotion, {
44
+ country_code: 'cl',
45
+ product: 'car_insurance',
46
+ rules: {
47
+ 'age' => { 'conditions' => [{ 'between' => [18, 65] }] },
48
+ 'gender' => { 'conditions' => [{ 'eq' => 'male' }]}
49
+ }
50
+ })
51
+ ].to_json)
52
+
53
+ result = Promonizr::Evaluations.evaluate(product, country_code, attributes)
54
+
55
+ expect(result).to_not be_nil
56
+ expect(result).to_not be_empty
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Promonizr::Products do
4
+
5
+ describe '.attributes_url' do
6
+ context 'when id is car_insurance' do
7
+ expected_url = CoConfig::PROMOTION_API[:url] + '/products/car_insurance/attributes'
8
+ it {expect(Promonizr::Products.attributes_url('car_insurance')).to eq(expected_url)}
9
+ end
10
+ end
11
+
12
+ describe '.advanced_url' do
13
+ context 'when id is car_insurance' do
14
+ expected_url = CoConfig::PROMOTION_API[:url] + '/products/car_insurance/advanced'
15
+ it {expect(Promonizr::Products.advanced_url('car_insurance')).to eq(expected_url)}
16
+ end
17
+ end
18
+
19
+ describe '.advanced' do
20
+ it {expect(Promonizr::Products.respond_to?(:advanced)).to be true}
21
+ end
22
+
23
+ describe '.attributes' do
24
+ it {expect(Promonizr::Products.respond_to?(:attributes)).to be true}
25
+ end
26
+
27
+ describe '#Products' do
28
+ it "does not contain credit_card" do
29
+ expect(Promonizr::Products::TYPE_CODES).not_to include('credit_card')
30
+ end
31
+ end
32
+
33
+ end