eeny-meeny 2.1.1 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4599a9361b4a7d72a01d42b9b992b3f2578ead2e
4
- data.tar.gz: 43fbfdad8d15903e61717a2575da3c18c8063321
3
+ metadata.gz: bc5619bbf033d3480a5da8522c1f092330143d4b
4
+ data.tar.gz: 0b164df787564d745c45556f761eaa53fa0a3877
5
5
  SHA512:
6
- metadata.gz: 1037418a1485bd6bac1754280ff70230d600e23014b11ca97bf063bada61d548ffda7d4dd98fce93ec6ba8f457a2e2c6cb04d021ee45bab314b68f533b2fb80f
7
- data.tar.gz: 281d612ec907b728eab11f6d9621b22962e27715c112d41a0de8262107d7bca6edf2f945d85047bfa740a716c93e25bb5fa6994adf800d276852bb418b6e0448
6
+ metadata.gz: d617820ad4bb8200af9b0879f15349724ccae3522975128f6797eaa909bb6560a0d9c1ee6cc3ada1b45d9452c7be61f75a7fdbe53bafdadd505574326f30b330
7
+ data.tar.gz: fe02125d91074ddca8a019e071162f883d7459a079df9050c12d5016abeb6126653704b1ce00dc69b641f2f4e05c77ee0ba5a593b471d18b6175edfdba81d75a
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ sudo: false
3
+ env:
4
+ - "RAILS_VERSION=4.2.8"
5
+ rvm:
6
+ - 2.0.0
7
+ - 2.1.10
8
+ - 2.2.7
9
+ - 2.3.4
10
+ - 2.4.1
@@ -1,3 +1,14 @@
1
+ ### 2.1.2 (2017-04-14)
2
+
3
+ Bugfixes:
4
+
5
+ - Fix validation regex for the 'smoke_test_id' query parameter.
6
+
7
+ Other Changes:
8
+
9
+ - Clean up the way cookies are written to the HTTP_COOKIE header.
10
+ - Added Travis CI and build status badge.
11
+
1
12
  ### 2.1.1 (2016-10-06)
2
13
 
3
14
  Bugfixes:
data/README.md CHANGED
@@ -3,6 +3,7 @@ eeny-meeny
3
3
  [![Gem Version](https://badge.fury.io/rb/eeny-meeny.svg)](https://badge.fury.io/rb/eeny-meeny)
4
4
  [![Code Climate](https://codeclimate.com/github/corthmann/eeny-meeny/badges/gpa.svg)](https://codeclimate.com/github/corthmann/eeny-meeny)
5
5
  [![Test Coverage](https://codeclimate.com/github/corthmann/eeny-meeny/badges/coverage.svg)](https://codeclimate.com/github/corthmann/eeny-meeny/coverage)
6
+ [![Build Status](https://travis-ci.org/corthmann/eeny-meeny.svg?branch=master)](https://travis-ci.org/corthmann/eeny-meeny)
6
7
 
7
8
  Installation
8
9
  -------------
@@ -3,7 +3,7 @@ require File.expand_path('../lib/eeny-meeny/version', __FILE__)
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'eeny-meeny'
5
5
  s.version = EenyMeeny::VERSION.dup
6
- s.date = '2016-10-06'
6
+ s.date = '2017-04-14'
7
7
  s.summary = "A simple split and smoke testing tool for Rails"
8
8
  s.authors = ["Christian Orthmann"]
9
9
  s.email = 'christian.orthmann@gmail.com'
@@ -8,6 +8,11 @@ require 'eeny-meeny/models/cookie'
8
8
  module EenyMeeny
9
9
  class Middleware
10
10
 
11
+ # Headers
12
+ HTTP_COOKIE = 'HTTP_COOKIE'.freeze
13
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
14
+ QUERY_STRING = 'QUERY_STRING'.freeze
15
+
11
16
  def initialize(app)
12
17
  @app = app
13
18
  @experiments = EenyMeeny::Experiment.find_all
@@ -15,46 +20,38 @@ module EenyMeeny
15
20
  end
16
21
 
17
22
  def call(env)
18
- request = Rack::Request.new(env)
19
- cookies = request.cookies
20
- now = Time.zone.now
21
- new_cookies = {}
22
- existing_set_cookie_header = env['Set-Cookie']
23
+ cookies = Rack::Utils.parse_query(env[HTTP_COOKIE],';,') { |s| Rack::Utils.unescape(s) rescue s }
24
+ query_parameters = query_hash(env)
25
+ now = Time.zone.now
26
+ new_cookies = {}
23
27
  # Prepare for experiments.
24
28
  @experiments.each do |experiment|
25
29
  # Skip inactive experiments
26
30
  next unless experiment.active?(now)
27
- # Trigger experiment through query parmeters
31
+ # Trigger experiment through query parameters
28
32
  cookie_name = EenyMeeny::Cookie.cookie_name(experiment)
29
- has_experiment_trigger = EenyMeeny.config.query_parameters[:experiment] && request.params.has_key?(cookie_name)
33
+ has_experiment_trigger = EenyMeeny.config.query_parameters[:experiment] && query_parameters.key?(cookie_name)
30
34
  # skip experiments that already have a cookie
31
- if has_experiment_trigger || !cookies.has_key?(cookie_name)
32
- cookie = if has_experiment_trigger
33
- # Trigger experiment variation through query parameter.
34
- EenyMeeny::Cookie.create_for_experiment_variation(experiment, request.params[cookie_name].to_sym, @cookie_config)
35
- else
36
- EenyMeeny::Cookie.create_for_experiment(experiment, @cookie_config)
37
- end
38
- # Set HTTP_COOKIE header to enable experiment on first pageview
39
- env = add_http_cookie(env, cookie, precede: has_experiment_trigger)
40
- new_cookies[cookie.name] = cookie
41
- end
35
+ next unless has_experiment_trigger || !cookies.key?(cookie_name)
36
+ cookie = if has_experiment_trigger
37
+ # Trigger experiment variation through query parameter.
38
+ EenyMeeny::Cookie.create_for_experiment_variation(experiment, query_parameters[cookie_name].to_sym, @cookie_config)
39
+ else
40
+ EenyMeeny::Cookie.create_for_experiment(experiment, @cookie_config)
41
+ end
42
+ # Set HTTP_COOKIE header to enable experiment on first pageview
43
+ env = add_or_replace_http_cookie(env, cookie)
44
+ new_cookies[cookie.name] = cookie
42
45
  end
43
46
  # Prepare smoke tests (if enabled through query parameters)
44
47
  if EenyMeeny.config.query_parameters[:smoke_test]
45
- if request.params.has_key?('smoke_test_id') && (request.params['smoke_test_id'] =~ /[A-Za-z_]+/)
48
+ if query_parameters.key?('smoke_test_id') && (query_parameters['smoke_test_id'] =~ /\A[A-Za-z_]+\z/)
46
49
  # Set HTTP_COOKIE header to enable smoke test on first pageview
47
- cookie = EenyMeeny::Cookie.create_for_smoke_test(request.params['smoke_test_id'])
48
- env = add_http_cookie(env, cookie, precede: true)
50
+ cookie = EenyMeeny::Cookie.create_for_smoke_test(query_parameters['smoke_test_id'])
51
+ env = add_or_replace_http_cookie(env, cookie)
49
52
  new_cookies[cookie.name] = cookie
50
53
  end
51
54
  end
52
- # Clean up 'Set-Cookie' header.
53
- if existing_set_cookie_header.nil?
54
- env.delete('Set-Cookie')
55
- else
56
- env['Set-Cookie'] = existing_set_cookie_header
57
- end
58
55
  # Delegate to app
59
56
  status, headers, body = @app.call(env)
60
57
  response = Rack::Response.new(body, status, headers)
@@ -66,19 +63,22 @@ module EenyMeeny
66
63
  end
67
64
 
68
65
  private
69
- def add_http_cookie(env, cookie, precede: false)
70
- env['Set-Cookie'] = ''
71
- Rack::Utils.set_cookie_header!(env,
72
- cookie.name,
73
- cookie.to_h)
74
- env['HTTP_COOKIE'] = '' if env['HTTP_COOKIE'].nil?
75
- if precede
76
- # Prepend cookie to the 'HTTP_COOKIE' header. This ensures it overwrites existing cookies when present.
77
- env['HTTP_COOKIE'] = env['Set-Cookie'] + '; ' + env['HTTP_COOKIE']
78
- else
79
- env['HTTP_COOKIE'] += '; ' unless env['HTTP_COOKIE'].empty?
80
- env['HTTP_COOKIE'] += env['Set-Cookie']
81
- end
66
+
67
+ def query_hash(env)
68
+ # Query Params are only relevant if EenyMeeny.config have them enabled.
69
+ return {} unless EenyMeeny.config.query_parameters[:experiment] || EenyMeeny.config.query_parameters[:smoke_test]
70
+ # Query Params are only relevant to HTTP GET requests.
71
+ return {} unless env[REQUEST_METHOD] == 'GET'
72
+ Rack::Utils.parse_query(env[QUERY_STRING], '&;')
73
+ end
74
+
75
+ def add_or_replace_http_cookie(env, cookie)
76
+ cookie_name_escaped = Rack::Utils.escape(cookie.name)
77
+ cookie_string = "#{cookie_name_escaped}=#{Rack::Utils.escape(cookie.value)}"
78
+ env[HTTP_COOKIE] = '' if env[HTTP_COOKIE].nil?
79
+ return env if env[HTTP_COOKIE].sub!(/#{Regexp.escape(cookie_name_escaped)}=[^;]+/, cookie_string)
80
+ env[HTTP_COOKIE] += '; ' unless env[HTTP_COOKIE].empty?
81
+ env[HTTP_COOKIE] += cookie_string
82
82
  env
83
83
  end
84
84
  end
@@ -57,12 +57,8 @@ module EenyMeeny
57
57
 
58
58
  def self.read(cookie_string)
59
59
  return if cookie_string.nil? || cookie_string.empty?
60
- begin
61
- return cookie_string unless EenyMeeny.config.secure # Cookie encryption disabled.
62
- EenyMeeny.config.encryptor.decrypt(cookie_string)
63
- rescue
64
- nil # Return nil if cookie is invalid.
65
- end
60
+ return cookie_string unless EenyMeeny.config.secure # Cookie encryption disabled.
61
+ EenyMeeny.config.encryptor.decrypt(cookie_string)
66
62
  end
67
63
 
68
64
  def initialize(name: '', value: '', expires: 1.month.from_now, http_only: true, same_site: nil, path: nil)
@@ -9,11 +9,11 @@ module EenyMeeny
9
9
  initializer 'eeny_meeny.configure' do |app|
10
10
  # Configrue EenyMeeny (defaults set in eeny_meeny.rb)
11
11
  EenyMeeny.configure do |config|
12
- config.cookies = app.config.eeny_meeny[:cookies] if app.config.eeny_meeny.has_key?(:cookies)
13
- config.experiments = app.config.eeny_meeny[:experiments] if app.config.eeny_meeny.has_key?(:experiments)
14
- config.secret = app.config.eeny_meeny[:secret] if app.config.eeny_meeny.has_key?(:secret)
15
- config.secure = app.config.eeny_meeny[:secure] if app.config.eeny_meeny.has_key?(:secure)
16
- config.query_parameters = app.config.eeny_meeny[:query_parameters] if app.config.eeny_meeny.has_key?(:query_parameters)
12
+ config.cookies = app.config.eeny_meeny[:cookies] if app.config.eeny_meeny.key?(:cookies)
13
+ config.experiments = app.config.eeny_meeny[:experiments] if app.config.eeny_meeny.key?(:experiments)
14
+ config.secret = app.config.eeny_meeny[:secret] if app.config.eeny_meeny.key?(:secret)
15
+ config.secure = app.config.eeny_meeny[:secure] if app.config.eeny_meeny.key?(:secure)
16
+ config.query_parameters = app.config.eeny_meeny[:query_parameters] if app.config.eeny_meeny.key?(:query_parameters)
17
17
  end
18
18
  # Include Helpers in ActionController and ActionView
19
19
  ActionController::Base.send :include, EenyMeeny::ExperimentHelper
@@ -1,3 +1,3 @@
1
1
  module EenyMeeny
2
- VERSION = '2.1.1'
2
+ VERSION = '2.1.2'
3
3
  end
@@ -16,7 +16,7 @@ namespace :eeny_meeny do
16
16
 
17
17
  namespace :cookie do
18
18
  desc 'Create a valid EenyMeeny experiment cookie'
19
- task :experiment, [:experiment_id] => :environment do |t, args|
19
+ task :experiment, [:experiment_id] => :environment do |_, args|
20
20
  raise "Missing 'experiment_id' parameter" if (args['experiment_id'].nil? || args['experiment_id'].empty?)
21
21
  experiment_id = args['experiment_id'].to_sym
22
22
  cookie = write_cookie(experiment_id)
@@ -24,7 +24,7 @@ namespace :eeny_meeny do
24
24
  end
25
25
 
26
26
  desc 'Create a valid EenyMeeny experiment cookie for a specific variation'
27
- task :experiment_variation, [:experiment_id, :variation_id] => :environment do |t, args|
27
+ task :experiment_variation, [:experiment_id, :variation_id] => :environment do |_, args|
28
28
  raise "Missing 'experiment_id' parameter" if (args['experiment_id'].nil? || args['experiment_id'].empty?)
29
29
  raise "Missing 'variation_id' parameter" if (args['variation_id'].nil? || args['variation_id'].empty?)
30
30
  experiment_id = args['experiment_id'].to_sym
@@ -34,7 +34,7 @@ namespace :eeny_meeny do
34
34
  end
35
35
 
36
36
  desc 'Create a valid EenyMeeny smoke test cookie'
37
- task :smoke_test, [:smoke_test_id, :version] => :environment do |t, args|
37
+ task :smoke_test, [:smoke_test_id, :version] => :environment do |_, args|
38
38
  raise "Missing 'smoke_test_id' parameter" if (args['smoke_test_id'].nil? || args['smoke_test_id'].empty?)
39
39
  smoke_test_id = args['smoke_test_id']
40
40
  version = args['version'] || 1
@@ -32,13 +32,13 @@ describe EenyMeeny::ExperimentHelper, experiments: true do
32
32
 
33
33
  context 'and given a variation id' do
34
34
  let(:request_with_variation_cookie) do
35
- request.set_cookie(EenyMeeny::Cookie.create_for_experiment_variation(EenyMeeny::Experiment.find_by_id(:my_page), variation_id: :new).to_s)
35
+ request.set_cookie(EenyMeeny::Cookie.create_for_experiment_variation(EenyMeeny::Experiment.find_by_id(:my_page), :new).to_s)
36
36
  request
37
37
  end
38
38
 
39
39
  context 'that matches the variation id the cookie' do
40
40
  it "returns the user's experiment variation" do
41
- allow(subject).to receive(:cookies).and_return(request_with_cookie.cookie_jar)
41
+ allow(subject).to receive(:cookies).and_return(request_with_variation_cookie.cookie_jar)
42
42
  expect(subject.participates_in?(:my_page, variation_id: :new)).to be_a EenyMeeny::Variation
43
43
  expect(subject.participates_in?(:my_page, variation_id: 'new')).to be_a EenyMeeny::Variation
44
44
  end
@@ -46,7 +46,7 @@ describe EenyMeeny::ExperimentHelper, experiments: true do
46
46
 
47
47
  context 'that does not match the variation id the cookie' do
48
48
  it 'returns nil' do
49
- allow(subject).to receive(:cookies).and_return(request_with_cookie.cookie_jar)
49
+ allow(subject).to receive(:cookies).and_return(request_with_variation_cookie.cookie_jar)
50
50
  expect(subject.participates_in?(:my_page, variation_id: :old)).to be_nil
51
51
  expect(subject.participates_in?(:my_page, variation_id: 'old')).to be_nil
52
52
  end
@@ -71,15 +71,15 @@ describe EenyMeeny::Middleware do
71
71
  end
72
72
 
73
73
  context 'and given an experiment query parameter' do
74
- let(:request) { Rack::MockRequest.new(subject) }
74
+ let(:request) { Rack::MockRequest.new(initialize_app(secure: false)) }
75
75
 
76
76
  before(:example) do
77
77
  @response = request.get('/test?eeny_meeny_my_page_v1=old', 'CONTENT_TYPE' => 'text/plain')
78
78
  end
79
79
 
80
80
  it 'selects the correct variation' do
81
- modified_request = Rack::Request.new(app)
82
- expect(EenyMeeny::Cookie.read(modified_request.cookies['eeny_meeny_my_page_v1'])).to eq('old')
81
+ expect(app['HTTP_COOKIE']).to include('eeny_meeny_my_page_v1=old')
82
+ expect(app['HTTP_COOKIE']).to_not include('eeny_meeny_my_page_v1=new')
83
83
  end
84
84
 
85
85
  it "sets the 'HTTP_COOKIE' header on the request" do
@@ -15,6 +15,7 @@ class MockRackApp
15
15
  def [](key)
16
16
  @env[key]
17
17
  end
18
+ alias_method :fetch, :[]
18
19
 
19
20
  def []=(key,value)
20
21
  @env[key] = value
@@ -4,11 +4,10 @@ require 'codeclimate-test-reporter'
4
4
  require 'active_support/time'
5
5
 
6
6
  SimpleCov.start do
7
- formatter SimpleCov::Formatter::MultiFormatter[
7
+ formatter SimpleCov::Formatter::MultiFormatter.new([
8
8
  SimpleCov::Formatter::HTMLFormatter,
9
9
  SimpleCov::Formatter::RcovFormatter,
10
- CodeClimate::TestReporter::Formatter
11
- ]
10
+ CodeClimate::TestReporter::Formatter])
12
11
  add_group('EenyMeeny', 'lib/eeny-meeny')
13
12
  add_group('Rake Tasks', 'lib/tasks')
14
13
  add_group('Specs', 'spec')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eeny-meeny
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Orthmann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-06 00:00:00.000000000 Z
11
+ date: 2017-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -140,6 +140,7 @@ executables: []
140
140
  extensions: []
141
141
  extra_rdoc_files: []
142
142
  files:
143
+ - ".travis.yml"
143
144
  - CHANGELOG.md
144
145
  - Gemfile
145
146
  - LICENSE