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 +4 -4
- data/.travis.yml +10 -0
- data/CHANGELOG.md +11 -0
- data/README.md +1 -0
- data/eeny-meeny.gemspec +1 -1
- data/lib/eeny-meeny/middleware.rb +40 -40
- data/lib/eeny-meeny/models/cookie.rb +2 -6
- data/lib/eeny-meeny/railtie.rb +5 -5
- data/lib/eeny-meeny/version.rb +1 -1
- data/lib/tasks/cookie.rake +3 -3
- data/spec/eeny-meeny/experiment_helper_spec.rb +3 -3
- data/spec/eeny-meeny/middleware_spec.rb +3 -3
- data/spec/mock_rack_app.rb +1 -0
- data/spec/spec_helper.rb +2 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc5619bbf033d3480a5da8522c1f092330143d4b
|
4
|
+
data.tar.gz: 0b164df787564d745c45556f761eaa53fa0a3877
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d617820ad4bb8200af9b0879f15349724ccae3522975128f6797eaa909bb6560a0d9c1ee6cc3ada1b45d9452c7be61f75a7fdbe53bafdadd505574326f30b330
|
7
|
+
data.tar.gz: fe02125d91074ddca8a019e071162f883d7459a079df9050c12d5016abeb6126653704b1ce00dc69b641f2f4e05c77ee0ba5a593b471d18b6175edfdba81d75a
|
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -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
|
[](https://badge.fury.io/rb/eeny-meeny)
|
4
4
|
[](https://codeclimate.com/github/corthmann/eeny-meeny)
|
5
5
|
[](https://codeclimate.com/github/corthmann/eeny-meeny/coverage)
|
6
|
+
[](https://travis-ci.org/corthmann/eeny-meeny)
|
6
7
|
|
7
8
|
Installation
|
8
9
|
-------------
|
data/eeny-meeny.gemspec
CHANGED
@@ -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 = '
|
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
|
-
|
19
|
-
|
20
|
-
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
|
31
|
+
# Trigger experiment through query parameters
|
28
32
|
cookie_name = EenyMeeny::Cookie.cookie_name(experiment)
|
29
|
-
has_experiment_trigger = EenyMeeny.config.query_parameters[:experiment] &&
|
33
|
+
has_experiment_trigger = EenyMeeny.config.query_parameters[:experiment] && query_parameters.key?(cookie_name)
|
30
34
|
# skip experiments that already have a cookie
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
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(
|
48
|
-
env =
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
env[
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
61
|
-
|
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)
|
data/lib/eeny-meeny/railtie.rb
CHANGED
@@ -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.
|
13
|
-
config.experiments = app.config.eeny_meeny[:experiments] if app.config.eeny_meeny.
|
14
|
-
config.secret = app.config.eeny_meeny[:secret] if app.config.eeny_meeny.
|
15
|
-
config.secure = app.config.eeny_meeny[:secure] if app.config.eeny_meeny.
|
16
|
-
config.query_parameters = app.config.eeny_meeny[:query_parameters] if app.config.eeny_meeny.
|
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
|
data/lib/eeny-meeny/version.rb
CHANGED
data/lib/tasks/cookie.rake
CHANGED
@@ -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 |
|
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 |
|
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 |
|
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),
|
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(
|
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(
|
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(
|
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
|
-
|
82
|
-
expect(
|
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
|
data/spec/mock_rack_app.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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:
|
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
|