rack-trade_tracker 0.1.3
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 +7 -0
- data/Rakefile +6 -0
- data/Readme.md +67 -0
- data/lib/rack/trade_tracker.rb +27 -0
- data/lib/rack/trade_tracker/cookie.rb +52 -0
- data/lib/rack/trade_tracker/handler.rb +100 -0
- data/lib/rack/trade_tracker/parameters.rb +40 -0
- data/lib/rack/trade_tracker/parameters/delimited.rb +32 -0
- data/lib/rack/trade_tracker/parameters/paired.rb +23 -0
- data/lib/rack/trade_tracker/version.rb +5 -0
- data/spec/rack/trade_tracker/cookie_spec.rb +47 -0
- data/spec/rack/trade_tracker/handler_spec.rb +135 -0
- data/spec/rack/trade_tracker/parameters_spec.rb +75 -0
- data/spec/rack/trade_tracker_spec.rb +33 -0
- data/spec/spec_helper.rb +23 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f3f85ee71138c76c5ac68fd3ff519a6829a177d2
|
4
|
+
data.tar.gz: 653d8f2b1e06fb5e36dc6b78f2c7bf34375bcb55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a08bb9c02b1f192e47f168ee5f8f3f6abd5e1d7e9c5ac25b155a34cc746c974813d1e34c7fe86039a2c6b43524df94239afafd1fcacfccadc018e225b0161a88
|
7
|
+
data.tar.gz: 0e96ef4287638b1e31e7dcea710658e1210870b3599f0152ccdf4f7b2b350b3d45009c9078fcccfbbf26d8c804c428d2fa6fc96ca8d729eb2a7c9f4ffaa8a553
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Rack::TradeTracker
|
2
|
+
|
3
|
+
The `Rack::TradeTracker` gem is for use with the Trade Tracker affiliate network.
|
4
|
+
|
5
|
+
The gem provides a [Rack middleware](http://guides.rubyonrails.org/rails_on_rack.html) component to handle Trade Tracker's redirect mechanism.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'rack-trade_tracker'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install rack-trade_tracker
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Note that `domain:` and `path:` are required options and you will get an error if you don't provide these:
|
26
|
+
- Replace `'your-domain'` with your domain name minus `www`; e.g. `clickmechanic.com`.
|
27
|
+
- Replace `'your-path'` with the target path you have agreed with Trade Tracker; e.g. `/repair` (note the leading `/`)
|
28
|
+
|
29
|
+
> The gem makes no assumptions about where and how you implement the Trade Tracker JS script. You will need to do this yourself.
|
30
|
+
|
31
|
+
### `config.ru`
|
32
|
+
If you are running a basic Rack app, you can configure the middleware in your `config.ru` file:
|
33
|
+
```ruby
|
34
|
+
use Rack::TradeTracker, domain: 'your-domain', path: '/your_path'
|
35
|
+
```
|
36
|
+
|
37
|
+
### Rails
|
38
|
+
If you are using Rails, you can configure the rack middleware in `config/application.rb`, or one of the `config/environments/<environment>.rb` files, using one of the provided helper methods:
|
39
|
+
```Ruby
|
40
|
+
config.middleware.use('Rack::TradeTracker', domain: 'your-domain', path: 'your-path')
|
41
|
+
|
42
|
+
# or
|
43
|
+
config.middleware.insert_before(existing_middleware, 'Rack::TradeTracker', domain: 'your-domain', path: 'your-path')
|
44
|
+
|
45
|
+
# or
|
46
|
+
config.middleware.insert_after(existing_middleware, 'Rack::TradeTracker', domain: 'your-domain', path: 'your-path')
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
## Development
|
52
|
+
|
53
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
54
|
+
|
55
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ClickMechanic/rack-trade_tracker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
60
|
+
|
61
|
+
## License
|
62
|
+
|
63
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
64
|
+
|
65
|
+
## Code of Conduct
|
66
|
+
|
67
|
+
Everyone interacting in the Rack::TradeTracker project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ClickMechanic/rack-trade_tracker/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "trade_tracker/version"
|
2
|
+
require_relative "trade_tracker/parameters"
|
3
|
+
require_relative "trade_tracker/handler"
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
|
7
|
+
class TradeTracker
|
8
|
+
TRACKBACK_URL = 'http://tc.tradetracker.net'.freeze
|
9
|
+
|
10
|
+
InitializationError = Class.new(RuntimeError)
|
11
|
+
|
12
|
+
def initialize(app, options = {})
|
13
|
+
@app = app
|
14
|
+
raise InitializationError.new('options must include :domain') unless (@domain = options[:domain])
|
15
|
+
raise InitializationError.new('options must include :path') unless (@path = options[:path])
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
Handler.new(domain, path, app).call(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :app, :domain, :path
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Rack
|
2
|
+
class TradeTracker
|
3
|
+
|
4
|
+
class Cookie
|
5
|
+
NAME = 'TT2_%{campaign_id}'.freeze
|
6
|
+
DIGEST_PARAMS = [:campaign_id, :material_id, :affiliate_id, :reference].freeze
|
7
|
+
VALUE_PARAMS = [:material_id, :affiliate_id, :reference].freeze
|
8
|
+
PATH = '/'.freeze
|
9
|
+
|
10
|
+
def initialize(domain, parameters)
|
11
|
+
@domain, @parameters = domain, parameters
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
NAME % params_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def checksum
|
19
|
+
source = "CHK_#{DIGEST_PARAMS.map { |param| params_hash[param] }.join('::')}"
|
20
|
+
Digest::MD5.hexdigest(source)
|
21
|
+
end
|
22
|
+
|
23
|
+
def timestamp
|
24
|
+
Time.now.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def value
|
28
|
+
VALUE_PARAMS.map { |param| params_hash[param] }.tap do |attributes|
|
29
|
+
attributes << checksum << timestamp
|
30
|
+
end.join('::')
|
31
|
+
end
|
32
|
+
|
33
|
+
def as_hash
|
34
|
+
{
|
35
|
+
value: value,
|
36
|
+
domain: ".#{domain}",
|
37
|
+
path: PATH,
|
38
|
+
expires: 1.year.from_now
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :domain, :parameters
|
45
|
+
|
46
|
+
def params_hash
|
47
|
+
@_params_hash ||= parameters.to_hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'active_support/core_ext/integer/time'
|
2
|
+
|
3
|
+
require_relative 'cookie'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
class TradeTracker
|
7
|
+
|
8
|
+
class Handler
|
9
|
+
PARAMS_MAP = {campaign_id: :c,
|
10
|
+
material_id: :m,
|
11
|
+
affiliate_id: :a,
|
12
|
+
reference: :r,
|
13
|
+
redirect_url: :u}.freeze
|
14
|
+
|
15
|
+
LOGGER_REGEX = /logger/i
|
16
|
+
|
17
|
+
def initialize(domain, path, app)
|
18
|
+
@domain = domain
|
19
|
+
@path = path
|
20
|
+
@app = app
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
@env = env
|
25
|
+
@request = Rack::Request.new(env)
|
26
|
+
|
27
|
+
return @app.call(env) unless matches_path?
|
28
|
+
|
29
|
+
begin
|
30
|
+
redirect
|
31
|
+
rescue Parameters::MissingParametersError
|
32
|
+
redirect_to_root
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :domain, :path, :app, :env, :request, :cookie
|
39
|
+
|
40
|
+
def matches_path?
|
41
|
+
request.path == path
|
42
|
+
end
|
43
|
+
|
44
|
+
def parameters
|
45
|
+
@_parameters ||= Parameters.new(request.params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def params_hash
|
49
|
+
@_params_hash ||= parameters.to_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def redirect_to_root
|
53
|
+
response(302, {'Location' => request.base_url} ) do
|
54
|
+
log("Redirecting to root as Trade Tracker parameters missing", :error)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def redirect
|
59
|
+
response(301, {'Location' => redirect_url} ) do |response|
|
60
|
+
set_cookie(response)
|
61
|
+
set_p3p_header(response)
|
62
|
+
log("Redirecting to Trade Tracker with cookie: #{URI.encode(cookie.value)}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def redirect_url
|
67
|
+
url = URI(TRACKBACK_URL)
|
68
|
+
params = PARAMS_MAP.keys.each_with_object({}) { |param, result| result[PARAMS_MAP[param]] = params_hash[param] }
|
69
|
+
url.query = params.map { |k,v| "#{k}=#{URI.encode(v)}" }.join('&')
|
70
|
+
url.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def response(status, header)
|
74
|
+
Rack::Response.new([], status, header).tap do |response|
|
75
|
+
yield response if block_given?
|
76
|
+
end.finish
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_cookie(response)
|
80
|
+
@cookie ||= Cookie.new(domain, parameters)
|
81
|
+
response.set_cookie(cookie.name, cookie.as_hash)
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_p3p_header(response)
|
85
|
+
response.headers['P3P'] = 'CP="ALL PUR DSP CUR ADMi DEVi CONi OUR COR IND"'
|
86
|
+
end
|
87
|
+
|
88
|
+
def log(message, level = :info)
|
89
|
+
return unless logger
|
90
|
+
|
91
|
+
logger.send(level, message) if logger.respond_to?(level)
|
92
|
+
end
|
93
|
+
|
94
|
+
def logger
|
95
|
+
@logger ||= env.find { |key, _| LOGGER_REGEX.match(key) }&.[] 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
|
3
|
+
require_relative 'parameters/paired'
|
4
|
+
require_relative 'parameters/delimited'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
class TradeTracker
|
8
|
+
class Parameters
|
9
|
+
CAMPAIGN_ID_PARAM = 'campaignID'.freeze
|
10
|
+
TT_PARAM = 'tt'.freeze
|
11
|
+
MISSING_PARAM_VALUE = ''.freeze
|
12
|
+
PERMITTED_PARAMS = %w(campaignID materialID affiliateID reference).freeze
|
13
|
+
|
14
|
+
MissingParametersError = Class.new(RuntimeError)
|
15
|
+
|
16
|
+
|
17
|
+
def initialize(params)
|
18
|
+
@params = params
|
19
|
+
if params.include?(CAMPAIGN_ID_PARAM)
|
20
|
+
extend Paired
|
21
|
+
elsif params.include?(TT_PARAM)
|
22
|
+
extend Delimited
|
23
|
+
else
|
24
|
+
fail MissingParametersError.new("URL must include either '#{CAMPAIGN_ID_PARAM}' or '#{TT_PARAM}' parameter")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
PERMITTED_PARAMS.each_with_object({}) do |param, result|
|
30
|
+
key = param.underscore.to_sym
|
31
|
+
result[key] = send(key)
|
32
|
+
end.merge(redirect_url: redirect_url)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :params
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../parameters'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class TradeTracker
|
5
|
+
class Parameters
|
6
|
+
|
7
|
+
module Delimited
|
8
|
+
DELIMITER = '_'.freeze
|
9
|
+
REDIRECT_PARAM = 'r'.freeze
|
10
|
+
|
11
|
+
def self.extended(base)
|
12
|
+
values = base.instance_eval do
|
13
|
+
param = params[TT_PARAM]
|
14
|
+
param.present? ? param.split(DELIMITER) : []
|
15
|
+
end
|
16
|
+
|
17
|
+
PERMITTED_PARAMS.each_with_index do |param, index|
|
18
|
+
define_method param.underscore do
|
19
|
+
values[index] || MISSING_PARAM_VALUE
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def redirect_url
|
26
|
+
params[REDIRECT_PARAM] || MISSING_PARAM_VALUE
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative '../parameters'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class TradeTracker
|
5
|
+
class Parameters
|
6
|
+
|
7
|
+
module Paired
|
8
|
+
REDIRECT_PARAM = 'redirectURL'.freeze
|
9
|
+
|
10
|
+
def self.extended(base)
|
11
|
+
params = base.send(:params)
|
12
|
+
|
13
|
+
(PERMITTED_PARAMS.dup << REDIRECT_PARAM).each do |param|
|
14
|
+
define_method param.underscore do
|
15
|
+
params[param] || MISSING_PARAM_VALUE
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Rack::TradeTracker::Cookie do
|
4
|
+
let(:params_hash) { {campaign_id: 'ABCDEF', material_id: '123456', affiliate_id: 'ABC123', reference: 'ref'} }
|
5
|
+
let(:parameters) { double :parameters, params_hash.merge(to_hash: params_hash) }
|
6
|
+
let(:domain) { 'test.com' }
|
7
|
+
let(:time_now) { Time.now }
|
8
|
+
let(:digest) { Digest::MD5.hexdigest('CHK_ABCDEF::123456::ABC123::ref') }
|
9
|
+
|
10
|
+
before { allow(Time).to receive(:now).and_return(time_now) }
|
11
|
+
|
12
|
+
subject { Rack::TradeTracker::Cookie.new(domain,parameters) }
|
13
|
+
|
14
|
+
it 'is named after the campaign_id' do
|
15
|
+
expect(subject.name).to eq 'TT2_ABCDEF'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'generates a checksum' do
|
19
|
+
expect(subject.checksum).to eq digest
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'generates a UNIX timestamp' do
|
23
|
+
expect(subject.timestamp).to eq time_now.to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#value' do
|
27
|
+
it 'combines material_id, affiliate_id, reference, checkSum and timeStamp' do
|
28
|
+
expected = "123456::ABC123::ref::#{digest}::#{time_now.to_i}"
|
29
|
+
expect(subject.value).to eq expected
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'as_hash' do
|
34
|
+
it 'includes the value' do
|
35
|
+
expect(subject.as_hash[:value]).to eq subject.value
|
36
|
+
end
|
37
|
+
it 'includes the domain' do
|
38
|
+
expect(subject.as_hash[:domain]).to eq ".#{domain}"
|
39
|
+
end
|
40
|
+
it 'includes the path' do
|
41
|
+
expect(subject.as_hash[:path]).to eq '/'
|
42
|
+
end
|
43
|
+
it 'includes the expiry' do
|
44
|
+
expect(subject.as_hash[:expires]).to eq time_now + 1.year
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Rack::TradeTracker::Handler do
|
4
|
+
let(:app) { ->(env) { [200, env, 'app'] } }
|
5
|
+
let(:domain) { 'test.com' }
|
6
|
+
let(:path) { '/path' }
|
7
|
+
|
8
|
+
subject { Rack::TradeTracker::Handler.new(domain, path, app) }
|
9
|
+
|
10
|
+
describe 'call' do
|
11
|
+
let(:env) { env_for(url) }
|
12
|
+
|
13
|
+
context 'with an alternate path' do
|
14
|
+
let(:url) { 'http://www.example.com/some-other-path' }
|
15
|
+
|
16
|
+
it 'passes the call along' do
|
17
|
+
expect(app).to receive(:call).with(env)
|
18
|
+
subject.call(env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with a matching path' do
|
23
|
+
let(:url) { URI.escape("http://www.example.com/path?#{params}") }
|
24
|
+
let(:cookie) { double :cookie, name: 'TT2_ABCDEF', value: '123456::ABC123::ref', as_hash: {value: '123456::ABC123::ref',
|
25
|
+
expires: 1.year.from_now,
|
26
|
+
path: '/',
|
27
|
+
domain: domain} }
|
28
|
+
|
29
|
+
shared_examples 'redirects with cookie' do |redirect_url|
|
30
|
+
attr_reader :status, :headers, :body, :trackback_url, :query
|
31
|
+
|
32
|
+
before do
|
33
|
+
allow(Rack::TradeTracker::Cookie).to receive(:new).and_return(cookie)
|
34
|
+
@status, @headers, @body = subject.call(env)
|
35
|
+
url = URI(headers['Location'])
|
36
|
+
@trackback_url = URI::HTTP.build(host: url.host).to_s
|
37
|
+
@query = url.query.split('&').map { |attr| attr.split('=', -1) }.to_h if url.query
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'redirects to the trackback URL' do
|
41
|
+
expect(status).to eq 301
|
42
|
+
expect(trackback_url).to eq Rack::TradeTracker::TRACKBACK_URL
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'includes the campaign_id parameter' do
|
46
|
+
expect(query['c']).to eq 'ABCDEF'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'includes the material_id parameter' do
|
50
|
+
expect(query['m']).to eq '123456'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'includes the affiliate_id parameter' do
|
54
|
+
expect(status).to eq 301
|
55
|
+
expect(query['a']).to eq 'ABC123'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'includes the redirect_url parameter' do
|
59
|
+
expect(status).to eq 301
|
60
|
+
expect(query['u']).to eq redirect_url
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'creates the Trade Tracker cookie' do
|
64
|
+
cookie_value = "123456%3A%3AABC123%3A%3Aref; domain=test.com; path=/; expires=#{Rack::Utils.rfc2822(cookie.as_hash[:expires].utc)}"
|
65
|
+
expect(headers['set-cookie']).to eq "#{cookie.name}=#{cookie_value}"
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'adds the P3P header' do
|
69
|
+
expect(headers['P3P']).to eq 'CP="ALL PUR DSP CUR ADMi DEVi CONi OUR COR IND"'
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with a logger defined in the rack env' do
|
73
|
+
let(:logger) { double :logger, info: nil }
|
74
|
+
|
75
|
+
before { env['rack.logger'] = logger }
|
76
|
+
|
77
|
+
it 'logs the redirect' do
|
78
|
+
subject.call(env)
|
79
|
+
expect(logger).to have_received(:info).with("Redirecting to Trade Tracker with cookie: #{URI.encode('123456::ABC123::ref')}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'with paired parameters' do
|
85
|
+
include_examples 'redirects with cookie', 'www.your-proper-url.com' do
|
86
|
+
let(:params) { 'campaignID=ABCDEF&materialID=123456&affiliateID=ABC123&redirectURL=www.your-proper-url.com' }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'with missing redirect URL' do
|
90
|
+
include_examples 'redirects with cookie', '' do
|
91
|
+
let(:params) { 'campaignID=ABCDEF&materialID=123456&affiliateID=ABC123' }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'with delimited parameters' do
|
97
|
+
include_examples 'redirects with cookie', 'www.your-proper-url.com' do
|
98
|
+
let(:params) { 'tt=ABCDEF_123456_ABC123_ref&r=www.your-proper-url.com' }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'with missing redirect URL' do
|
102
|
+
include_examples 'redirects with cookie', '' do
|
103
|
+
let(:params) { 'tt=ABCDEF_123456_ABC123_ref' }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'with missing campaignID and tt parameters' do
|
109
|
+
let(:params) { '' }
|
110
|
+
|
111
|
+
it 'redirects to root with 302' do
|
112
|
+
status, headers, body = subject.call(env)
|
113
|
+
expect(status).to eq 302
|
114
|
+
expect(headers['Location']).to eq 'http://www.example.com'
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'does not forward the request' do
|
118
|
+
expect(app).not_to receive(:call)
|
119
|
+
subject.call(env)
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'with a logger defined in the rack env' do
|
123
|
+
let(:logger) { double :logger, error: nil }
|
124
|
+
|
125
|
+
before { env['rack.logger'] = logger }
|
126
|
+
|
127
|
+
it 'logs the redirect' do
|
128
|
+
subject.call(env)
|
129
|
+
expect(logger).to have_received(:error).with('Redirecting to root as Trade Tracker parameters missing')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Rack::TradeTracker::Parameters do
|
4
|
+
let(:request) { double :request, params: params }
|
5
|
+
subject { Rack::TradeTracker::Parameters.new(request.params) }
|
6
|
+
|
7
|
+
context 'when request includes campaignID parameter' do
|
8
|
+
let(:params) { {'campaignID' => 'a-campaign-id',
|
9
|
+
'materialID' => 'a-material-id',
|
10
|
+
'affiliateID' => 'an-affiliate-id',
|
11
|
+
'redirectURL' => 'www.your-proper-url.com',
|
12
|
+
}}
|
13
|
+
|
14
|
+
it 'extracts the parameters correctly' do
|
15
|
+
params.each do |key, value|
|
16
|
+
expect(subject.send(key.underscore)).to eq value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'converts to a hash' do
|
21
|
+
expected = {campaign_id: subject.campaign_id,
|
22
|
+
material_id: subject.material_id,
|
23
|
+
affiliate_id: subject.affiliate_id,
|
24
|
+
reference: subject.reference,
|
25
|
+
redirect_url: subject.redirect_url}
|
26
|
+
|
27
|
+
expect(subject.to_hash).to eq expected
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with a missing parameter' do
|
31
|
+
before { params.delete('materialID') }
|
32
|
+
|
33
|
+
it 'renders empty string for the missing parameter' do
|
34
|
+
expect(subject.material_id).to eq ''
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'when request includes tt parameter' do
|
40
|
+
(1..4).each do |num_params|
|
41
|
+
context "with #{num_params} values" do
|
42
|
+
let(:provided_params) { Rack::TradeTracker::Parameters::PERMITTED_PARAMS[0..(num_params - 1)] }
|
43
|
+
let(:params) { {'tt' => num_params.times.map(&:to_s).join('_'), 'r' => 'www.your-proper-url.com'} }
|
44
|
+
|
45
|
+
it 'extracts the parameters correctly' do
|
46
|
+
provided_params.each_with_index do |param, index|
|
47
|
+
expect(subject.send(param.underscore)).to eq index.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'converts to a hash' do
|
52
|
+
expected = {campaign_id: subject.campaign_id,
|
53
|
+
material_id: subject.material_id,
|
54
|
+
affiliate_id: subject.affiliate_id,
|
55
|
+
reference: subject.reference,
|
56
|
+
redirect_url: subject.redirect_url}
|
57
|
+
|
58
|
+
expect(subject.to_hash).to eq expected
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'extracts the redirect url' do
|
62
|
+
expect(subject.redirect_url).to eq 'www.your-proper-url.com'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with missing tt and campaignID parameters' do
|
68
|
+
let(:params) { {} }
|
69
|
+
|
70
|
+
it 'fails with MissingParametersError' do
|
71
|
+
expect{ subject }.to raise_error Rack::TradeTracker::Parameters::MissingParametersError
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Rack::TradeTracker do
|
4
|
+
let(:app) { ->(env) { [200, env, 'app'] } }
|
5
|
+
let(:domain) { 'test.com' }
|
6
|
+
let(:path) { '/path' }
|
7
|
+
|
8
|
+
subject { Rack::TradeTracker.new(app, domain: domain, path: path) }
|
9
|
+
|
10
|
+
it 'has a version number' do
|
11
|
+
expect(Rack::TradeTracker::VERSION).not_to be nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'requires a domain' do
|
15
|
+
expect { Rack::TradeTracker.new(app, path: path) }.to raise_error(Rack::TradeTracker::InitializationError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'requires a path' do
|
19
|
+
expect { Rack::TradeTracker.new(app, domain: domain) }.to raise_error(Rack::TradeTracker::InitializationError)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'call' do
|
23
|
+
let(:env) { env_for(url) }
|
24
|
+
let(:url) { 'http://www.example.com/path' }
|
25
|
+
|
26
|
+
it 'delegates to the Handler' do
|
27
|
+
handler = double(:handler)
|
28
|
+
allow(Rack::TradeTracker::Handler).to receive(:new).with(domain, path, app).and_return(handler)
|
29
|
+
expect(handler).to receive(:call).with(env)
|
30
|
+
subject.call(env)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require 'rack/mock'
|
3
|
+
require "rack/trade_tracker"
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
def env_for(url)
|
7
|
+
Rack::MockRequest.env_for(url)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
# Enable flags like --only-failures and --next-failure
|
13
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
14
|
+
|
15
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
16
|
+
config.disable_monkey_patching!
|
17
|
+
|
18
|
+
config.expect_with :rspec do |c|
|
19
|
+
c.syntax = :expect
|
20
|
+
end
|
21
|
+
|
22
|
+
config.include Helpers
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-trade_tracker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Forrest
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec_junit_formatter
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: The Rack::TradeTracker gem is for use with the Trade Tracker affiliate
|
98
|
+
network. The gem provides a Rack middleware component to handle Trade Tracker's
|
99
|
+
redirect mechanism.
|
100
|
+
email:
|
101
|
+
- ben@clickmechanic.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- Rakefile
|
107
|
+
- Readme.md
|
108
|
+
- lib/rack/trade_tracker.rb
|
109
|
+
- lib/rack/trade_tracker/cookie.rb
|
110
|
+
- lib/rack/trade_tracker/handler.rb
|
111
|
+
- lib/rack/trade_tracker/parameters.rb
|
112
|
+
- lib/rack/trade_tracker/parameters/delimited.rb
|
113
|
+
- lib/rack/trade_tracker/parameters/paired.rb
|
114
|
+
- lib/rack/trade_tracker/version.rb
|
115
|
+
- spec/rack/trade_tracker/cookie_spec.rb
|
116
|
+
- spec/rack/trade_tracker/handler_spec.rb
|
117
|
+
- spec/rack/trade_tracker/parameters_spec.rb
|
118
|
+
- spec/rack/trade_tracker_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
homepage: https://github.com/ClickMechanic/rack-trade_tracker
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata:
|
124
|
+
allowed_push_host: https://rubygems.org
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.6.12
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Provides TradeTracker redirect endpoint as Rack middleware
|
145
|
+
test_files: []
|