fluidfeatures 0.4.1 → 0.4.4
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.
- data/.gitignore +2 -0
- data/.travis.yml +7 -0
- data/Guardfile +6 -0
- data/Rakefile +3 -0
- data/fluidfeatures.gemspec +7 -0
- data/lib/fluidfeatures/app/feature.rb +2 -2
- data/lib/fluidfeatures/app/reporter.rb +13 -14
- data/lib/fluidfeatures/app/state.rb +11 -12
- data/lib/fluidfeatures/app/transaction.rb +4 -5
- data/lib/fluidfeatures/app/user.rb +1 -1
- data/lib/fluidfeatures/client.rb +3 -2
- data/lib/fluidfeatures/version.rb +1 -1
- data/spec/app/feature_spec.rb +58 -0
- data/spec/app/reporter_spec.rb +127 -0
- data/spec/app/state_spec.rb +75 -0
- data/spec/app/transaction_spec.rb +127 -0
- data/spec/app/user_spec.rb +69 -0
- data/spec/app_spec.rb +88 -0
- data/spec/cassettes/feature.yml +401 -0
- data/spec/cassettes/goal.yml +441 -0
- data/spec/fluidfeatures_spec.rb +53 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/api_helpers.rb +27 -0
- data/spec/support/polling_loop_shared_examples.rb +42 -0
- metadata +114 -2
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FluidFeatures::AppState do
|
4
|
+
|
5
|
+
it_should_behave_like "polling loop", :load_state
|
6
|
+
|
7
|
+
context do
|
8
|
+
let(:state) { described_class.new(app) }
|
9
|
+
|
10
|
+
let(:features) { { "feature" => { "a" => true } } }
|
11
|
+
|
12
|
+
let(:app) { mock "FluidFeatures::App", client: mock("client", uuid: 'client uuid'), logger: mock('logger') }
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
app.stub!(:is_a?).and_return(false)
|
16
|
+
app.stub!(:is_a?).with(FluidFeatures::App).and_return(true)
|
17
|
+
described_class.any_instance.stub(:run_loop)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#load_state" do
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
app.stub!(:get)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return false if getting state from server failed" do
|
27
|
+
app.stub!(:get).and_return(false, nil)
|
28
|
+
state.load_state.should == [false, nil]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return true and state if getting state from server succeeded" do
|
32
|
+
app.stub!(:get).and_return([true, {
|
33
|
+
"feature" => { "versions" => { "a" => { "parts" => [1, 2, 3] } } }
|
34
|
+
}])
|
35
|
+
state.load_state.should == [true, "feature" => { "versions" => { "a" => { "parts" => Set.new([1, 2, 3]) } } }]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#feature_version_enabled_for_user" do
|
41
|
+
let(:features) { { "Feature" => { "num_parts" => 3, "versions" => { "a" => { "parts" => [1, 3, 5], "enabled" => { "attributes" => { "key" => ["id"] }} } } } } }
|
42
|
+
|
43
|
+
before(:each) do
|
44
|
+
state.stub!(:features).and_return features
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should raise error if feature name is invalid" do
|
48
|
+
expect { state.feature_version_enabled_for_user(0, "a", "123") }.to raise_error /feature_name invalid/
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should raise error if version name is invalid" do
|
52
|
+
expect { state.feature_version_enabled_for_user("Feature", 0, "123") }.to raise_error /version_name invalid/
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return true if user id/parts number modulus included in feature parts" do
|
56
|
+
state.feature_version_enabled_for_user("Feature", "a", 4).should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should return true if string user id/parts number modulus included in feature parts" do
|
60
|
+
state.feature_version_enabled_for_user("Feature", "a", "4").should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return false if user id/parts number modulus not included in feature parts" do
|
64
|
+
state.feature_version_enabled_for_user("Feature", "a", 5).should be_false
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should return true if stated implicitly" do
|
68
|
+
state.feature_version_enabled_for_user("Feature", "a", 5, { "key" => "id" }).should be_true
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FluidFeatures::AppUserTransaction do
|
4
|
+
|
5
|
+
let(:user) { mock "user" }
|
6
|
+
let(:features) { { "Feature" => { "num_parts" => 3, "a" => { "parts" => [1, 3, 5], "enabled" => { "attributes" => { "key" => ["id"] } } }, "versions" => { "a" => { "parts" => [1, 3, 5], "enabled" => { "attributes" => { "key" => ["id"] } } } } } } }
|
7
|
+
let(:transaction){ described_class.new(user, "/url") }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
user.stub!(:features).and_return(features)
|
11
|
+
user.stub!(:is_a?).and_return(false)
|
12
|
+
user.stub!(:is_a?).with(FluidFeatures::AppUser).and_return(true)
|
13
|
+
transaction.stub(:ended).and_return(false)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should initialize instance variables with passed values" do
|
17
|
+
transaction.user.should == user
|
18
|
+
transaction.url.should == "/url"
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#feature_enabled?" do
|
22
|
+
|
23
|
+
it "should add known feature to features hit" do
|
24
|
+
transaction.should_not_receive(:unknown_feature_hit)
|
25
|
+
transaction.feature_enabled?("Feature", "a", true)
|
26
|
+
transaction.features_hit["Feature"]["a"].should == Hash.new
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should call unknown_feature_hit for unknown feature" do
|
30
|
+
transaction.should_receive(:unknown_feature_hit).with("Feature", "b", true)
|
31
|
+
transaction.feature_enabled?("Feature", "b", true)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should raise error if transaction ended" do
|
35
|
+
transaction.stub!(:ended).and_return(true)
|
36
|
+
expect { transaction.feature_enabled?("Feature", "a", true) }.to raise_error /transaction ended/
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise error if feature name invalid" do
|
40
|
+
expect { transaction.feature_enabled?(0, "a", true) }.to raise_error /feature_name invalid/
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#unknown_feature_hit" do
|
45
|
+
|
46
|
+
it "should add passed feature to feature hits" do
|
47
|
+
transaction.unknown_feature_hit("Feature", "a", true)
|
48
|
+
transaction.unknown_features["Feature"]["a"].should == true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should raise error if transaction ended" do
|
52
|
+
transaction.stub!(:ended).and_return(true)
|
53
|
+
expect { transaction.unknown_feature_hit("Feature", "a", true) }.to raise_error /transaction ended/
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#goal_hit" do
|
59
|
+
|
60
|
+
it "should add passed goal to goal hits" do
|
61
|
+
transaction.goal_hit("Goal", "default")
|
62
|
+
transaction.goals_hit["Goal"]["default"].should == Hash.new
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise error if transaction ended" do
|
66
|
+
transaction.stub!(:ended).and_return(true)
|
67
|
+
expect { transaction.goal_hit("Goal", "default") }.to raise_error /transaction ended/
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should raise error if goal name invalid" do
|
71
|
+
expect { transaction.goal_hit(0, "default") }.to raise_error /goal_name invalid/
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should raise error if goal version name invalid" do
|
75
|
+
expect { transaction.goal_hit("Goal", 0) }.to raise_error /goal_version_name invalid/
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#duration" do
|
81
|
+
it "should calculate difference between now and transaction start" do
|
82
|
+
now = Time.now
|
83
|
+
start_time = now - 3600
|
84
|
+
Time.stub!(:now).and_return(now)
|
85
|
+
transaction.stub!(:start_time).and_return(start_time)
|
86
|
+
transaction.duration.should == 3600
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should return saved duration if ended" do
|
90
|
+
transaction.stub!(:ended).and_return(true)
|
91
|
+
transaction.instance_variable_set(:@duration, 60)
|
92
|
+
transaction.duration.should == 60
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#end_transaction" do
|
97
|
+
let!(:reporter) { mock("reporter") }
|
98
|
+
|
99
|
+
before(:each) do
|
100
|
+
transaction.stub_chain(:user, :app, :reporter).and_return(reporter)
|
101
|
+
reporter.stub!(:report_transaction)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should raise error if transaction ended" do
|
105
|
+
transaction.stub!(:ended).and_return(true)
|
106
|
+
expect { transaction.end_transaction }.to raise_error /transaction ended/
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should report transaction" do
|
110
|
+
reporter.should_receive(:report_transaction).with(transaction)
|
111
|
+
transaction.end_transaction
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should save duration at transaction end" do
|
115
|
+
transaction.should_receive(:duration).and_return(100)
|
116
|
+
transaction.end_transaction
|
117
|
+
transaction.instance_variable_get(:@duration).should == 100
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should mark transaction ended" do
|
121
|
+
transaction.end_transaction
|
122
|
+
transaction.instance_variable_get(:@ended).should be_true
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe FluidFeatures::AppUser do
|
4
|
+
|
5
|
+
let(:app) { mock "app", state: mock('state'), client: mock("client", uuid: 'client uuid'), logger: mock('logger') }
|
6
|
+
let(:user_params) { [app, "user_id", "John Doe", false, {}, {}] }
|
7
|
+
let(:user) { described_class.new(*user_params) }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
app.stub!(:is_a?).and_return(false)
|
11
|
+
app.stub!(:is_a?).with(FluidFeatures::App).and_return(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "initialization" do
|
15
|
+
|
16
|
+
it "should raise error if invalid application passed" do
|
17
|
+
app.stub!(:is_a?).with(FluidFeatures::App).and_return(false)
|
18
|
+
expect { described_class.new(*user_params) }.to raise_error /app invalid/
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should initialize instance variables with passed values" do
|
22
|
+
user.app.should == app
|
23
|
+
user.unique_id.should == "user_id"
|
24
|
+
user.display_name.should == "John Doe"
|
25
|
+
user.anonymous.should be_false
|
26
|
+
user.unique_attrs = {}
|
27
|
+
user.cohort_attrs = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should generate id for anonymous user" do
|
31
|
+
user = described_class.new(app, nil, "John Doe", true, {}, {})
|
32
|
+
user.unique_id.should match /anon-/
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "#get should proxy to app" do
|
37
|
+
app.should_receive(:get).with("/user/user_id/url", nil)
|
38
|
+
user.get("/url")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "#post should proxy to app" do
|
42
|
+
app.should_receive(:post).with("/user/user_id/url", "payload")
|
43
|
+
user.post("/url", "payload")
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#features" do
|
47
|
+
let(:features) { { "Feature" => { "num_parts" => 3, "versions" => { "a" => { "parts" => [1, 3, 5], "enabled" => { "attributes" => { "key" => ["id"] }} } } } } }
|
48
|
+
|
49
|
+
it "should get features from state" do
|
50
|
+
ENV["FLUIDFEATURES_USER_FEATURES_FROM_API"] = nil
|
51
|
+
app.should_not_receive(:get)
|
52
|
+
app.state.should_receive(:features).and_return(features)
|
53
|
+
app.state.should_receive(:feature_version_enabled_for_user).and_return(true)
|
54
|
+
user.features.should == { "Feature" => { "a" => true } }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should get features from api if env variable set" do
|
58
|
+
ENV["FLUIDFEATURES_USER_FEATURES_FROM_API"] = "true"
|
59
|
+
app.should_receive(:get).with("/user/user_id/features", {:anonymous=>"false"})
|
60
|
+
user.features
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
it "#transaction should create transaction" do
|
66
|
+
FluidFeatures::AppUserTransaction.should_receive(:new).with(user, "/url")
|
67
|
+
user.transaction("/url")
|
68
|
+
end
|
69
|
+
end
|
data/spec/app_spec.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe FluidFeatures::App do
|
4
|
+
|
5
|
+
let(:client) { mock("client") }
|
6
|
+
|
7
|
+
let(:app) { FluidFeatures::App.new(client, "id", "secret", Logger.new(STDERR)) }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
client.stub!(:is_a?).and_return(false)
|
11
|
+
client.stub!(:is_a?).with(FluidFeatures::Client).and_return(true)
|
12
|
+
FluidFeatures::AppState.stub!(:new)
|
13
|
+
FluidFeatures::AppReporter.stub!(:new)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "initialization" do
|
17
|
+
|
18
|
+
it "should raise error if invalid client passed" do
|
19
|
+
client.stub!(:is_a?).with(FluidFeatures::Client).and_return(false)
|
20
|
+
expect { described_class.new(client, "", "", Logger.new(STDERR)) }.to raise_error /client invalid/
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise error if invalid app id passed" do
|
24
|
+
expect { described_class.new(client, 0, "", Logger.new(STDERR)) }.to raise_error /app_id invalid/
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise error if invalid secret passed" do
|
28
|
+
expect { described_class.new(client, "", 0, Logger.new(STDERR)) }.to raise_error /secret invalid/
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should initialize instance variables with passed values" do
|
32
|
+
app = described_class.new(client, "id", "secret", Logger.new(STDERR))
|
33
|
+
app.client.should == client
|
34
|
+
app.app_id.should == "id"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should start up reporter runner" do
|
38
|
+
FluidFeatures::AppState.should_receive(:new).with(app)
|
39
|
+
app
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should start up state runner" do
|
43
|
+
FluidFeatures::AppReporter.should_receive(:new).with(app)
|
44
|
+
app
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "#get should proxy to client" do
|
49
|
+
client.should_receive(:get).with("/app/id/url", "secret", nil, false)
|
50
|
+
app.get("/url")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "#put should proxy to client" do
|
54
|
+
client.should_receive(:put).with("/app/id/url", "secret", "payload")
|
55
|
+
app.put("/url", "payload")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "#post should proxy to client" do
|
59
|
+
client.should_receive(:post).with("/app/id/url", "secret", "payload")
|
60
|
+
app.post("/url", "payload")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "#feature should get features" do
|
64
|
+
app.should_receive(:get).with("/features")
|
65
|
+
app.features
|
66
|
+
end
|
67
|
+
|
68
|
+
it "#user should create user" do
|
69
|
+
attrs = ["user id", "John Doe", false, {}, {}]
|
70
|
+
FluidFeatures::AppUser.should_receive(:new).with(*[app] + attrs)
|
71
|
+
app.user(*attrs)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "#user_transaction should return user transaction" do
|
75
|
+
attrs = ["user id", "/url", "John Doe", false, {}, {}]
|
76
|
+
transaction = mock('transaction')
|
77
|
+
transaction.should_receive(:transaction).with("/url")
|
78
|
+
app.should_receive(:user).with(*attrs.reject {|a| a == '/url'}).and_return(transaction)
|
79
|
+
app.user_transaction(*attrs)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "#feature_version should create version" do
|
83
|
+
attrs = ["Feature", "a"]
|
84
|
+
FluidFeatures::AppFeatureVersion.should_receive(:new).with(*[app] + attrs)
|
85
|
+
app.feature_version(*attrs)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,401 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/features?verbose=true&etag_wait=30
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
accept:
|
11
|
+
- application/json
|
12
|
+
user-agent:
|
13
|
+
- Ruby
|
14
|
+
accept-encoding:
|
15
|
+
- gzip
|
16
|
+
connection:
|
17
|
+
- keep-alive
|
18
|
+
keep-alive:
|
19
|
+
- 30
|
20
|
+
response:
|
21
|
+
status:
|
22
|
+
code: 200
|
23
|
+
message: OK
|
24
|
+
headers:
|
25
|
+
cache-control:
|
26
|
+
- no-cache
|
27
|
+
content-type:
|
28
|
+
- text/html; charset=UTF-8
|
29
|
+
date:
|
30
|
+
- Mon, 03 Dec 2012 08:22:41 GMT
|
31
|
+
etag:
|
32
|
+
- ! '"36679fb05ecafe685b0dc553390ccb1df563da75"'
|
33
|
+
expires:
|
34
|
+
- Mon, 03 Dec 2012 08:22:40 GMT
|
35
|
+
server:
|
36
|
+
- TornadoServer/2.3
|
37
|
+
content-length:
|
38
|
+
- '684'
|
39
|
+
connection:
|
40
|
+
- keep-alive
|
41
|
+
body:
|
42
|
+
encoding: US-ASCII
|
43
|
+
string: ! '{"Feature": {"name": "Feature", "num_parts": 100, "versions": {"a":
|
44
|
+
{"stats": {"usage": {"percent": 33}}, "parts": [1, 6, 8, 9, 10, 15, 18, 19,
|
45
|
+
21, 23, 25, 26, 29, 30, 31, 33, 34, 37, 39, 40, 41, 47, 48, 52, 53, 56, 58,
|
46
|
+
59, 62, 65, 68, 70, 71, 74, 75, 77, 79, 80, 82, 83, 88, 89, 90, 91, 92, 93,
|
47
|
+
94, 95, 97, 99], "enabled": {"percent": 100}, "effective": {"percent": 50}},
|
48
|
+
"b": {"stats": {"usage": {"percent": 66}}, "parts": [2, 3, 4, 5, 7, 11, 12,
|
49
|
+
13, 14, 16, 17, 20, 22, 24, 27, 28, 32, 35, 36, 38, 42, 43, 44, 45, 46, 49,
|
50
|
+
50, 51, 54, 55, 57, 60, 61, 63, 64, 66, 67, 69, 72, 73, 76, 78, 81, 84, 85,
|
51
|
+
86, 87, 96, 98, 100], "enabled": {"percent": 100}, "effective": {"percent":
|
52
|
+
50}}}}}'
|
53
|
+
http_version: '1.1'
|
54
|
+
recorded_at: Mon, 03 Dec 2012 08:22:41 GMT
|
55
|
+
- request:
|
56
|
+
method: post
|
57
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/report/transactions
|
58
|
+
body:
|
59
|
+
encoding: UTF-8
|
60
|
+
string: ! '{"client_uuid":"86bcd9e0-1f50-0130-eb89-704da2623409","transactions":[{"url":"http://example.com","user":{"id":"anon-6903314482-48279","anonymous":true,"unique":[],"cohorts":[]},"hits":{"feature":{"Feature1":{"a":{},"b":{}}},"goal":{}},"stats":{"duration":3.8208e-05}}],"stats":{"waiting_buckets":[0]},"unknown_features":{"Feature1":{"a":true,"b":true}},"api_request_log":[]}'
|
61
|
+
headers:
|
62
|
+
accept:
|
63
|
+
- application/json
|
64
|
+
user-agent:
|
65
|
+
- Ruby
|
66
|
+
accept-encoding:
|
67
|
+
- gzip
|
68
|
+
content-type:
|
69
|
+
- application/json
|
70
|
+
connection:
|
71
|
+
- keep-alive
|
72
|
+
keep-alive:
|
73
|
+
- 30
|
74
|
+
response:
|
75
|
+
status:
|
76
|
+
code: 200
|
77
|
+
message: OK
|
78
|
+
headers:
|
79
|
+
cache-control:
|
80
|
+
- no-cache
|
81
|
+
content-type:
|
82
|
+
- text/html; charset=UTF-8
|
83
|
+
date:
|
84
|
+
- Mon, 03 Dec 2012 08:22:41 GMT
|
85
|
+
expires:
|
86
|
+
- Mon, 03 Dec 2012 08:22:40 GMT
|
87
|
+
server:
|
88
|
+
- TornadoServer/2.3
|
89
|
+
content-length:
|
90
|
+
- '0'
|
91
|
+
connection:
|
92
|
+
- keep-alive
|
93
|
+
body:
|
94
|
+
encoding: US-ASCII
|
95
|
+
string: ''
|
96
|
+
http_version: '1.1'
|
97
|
+
recorded_at: Mon, 03 Dec 2012 08:22:41 GMT
|
98
|
+
- request:
|
99
|
+
method: get
|
100
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/features
|
101
|
+
body:
|
102
|
+
encoding: US-ASCII
|
103
|
+
string: ''
|
104
|
+
headers:
|
105
|
+
accept:
|
106
|
+
- application/json
|
107
|
+
user-agent:
|
108
|
+
- Ruby
|
109
|
+
accept-encoding:
|
110
|
+
- gzip
|
111
|
+
connection:
|
112
|
+
- keep-alive
|
113
|
+
keep-alive:
|
114
|
+
- 30
|
115
|
+
response:
|
116
|
+
status:
|
117
|
+
code: 200
|
118
|
+
message: OK
|
119
|
+
headers:
|
120
|
+
cache-control:
|
121
|
+
- no-cache
|
122
|
+
content-type:
|
123
|
+
- text/html; charset=UTF-8
|
124
|
+
date:
|
125
|
+
- Mon, 03 Dec 2012 08:22:42 GMT
|
126
|
+
etag:
|
127
|
+
- ! '"2d117317916974c6ed4a713814642bb3cbe20144"'
|
128
|
+
expires:
|
129
|
+
- Mon, 03 Dec 2012 08:22:41 GMT
|
130
|
+
server:
|
131
|
+
- TornadoServer/2.3
|
132
|
+
content-length:
|
133
|
+
- '505'
|
134
|
+
connection:
|
135
|
+
- keep-alive
|
136
|
+
body:
|
137
|
+
encoding: US-ASCII
|
138
|
+
string: ! '{"Feature": {"name": "Feature", "versions": {"a": {"stats": {"usage":
|
139
|
+
{"percent": 25}}, "enabled": {"percent": 100}, "effective": {"percent": 50}},
|
140
|
+
"b": {"stats": {"usage": {"percent": 50}}, "enabled": {"percent": 100}, "effective":
|
141
|
+
{"percent": 50}}}}, "Feature1": {"name": "Feature1", "versions": {"a": {"stats":
|
142
|
+
{"usage": {"percent": 0}}, "enabled": {"percent": 100}, "effective": {"percent":
|
143
|
+
50}}, "b": {"stats": {"usage": {"percent": 25}}, "enabled": {"percent": 100},
|
144
|
+
"effective": {"percent": 50}}}}}'
|
145
|
+
http_version: '1.1'
|
146
|
+
recorded_at: Mon, 03 Dec 2012 08:22:42 GMT
|
147
|
+
- request:
|
148
|
+
method: get
|
149
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/features?verbose=true&etag_wait=30
|
150
|
+
body:
|
151
|
+
encoding: US-ASCII
|
152
|
+
string: ''
|
153
|
+
headers:
|
154
|
+
accept:
|
155
|
+
- application/json
|
156
|
+
user-agent:
|
157
|
+
- Ruby
|
158
|
+
accept-encoding:
|
159
|
+
- gzip
|
160
|
+
if-none-match:
|
161
|
+
- ! '"36679fb05ecafe685b0dc553390ccb1df563da75"'
|
162
|
+
connection:
|
163
|
+
- keep-alive
|
164
|
+
keep-alive:
|
165
|
+
- 30
|
166
|
+
response:
|
167
|
+
status:
|
168
|
+
code: 200
|
169
|
+
message: !binary |-
|
170
|
+
T0s=
|
171
|
+
headers:
|
172
|
+
!binary "Y2FjaGUtY29udHJvbA==":
|
173
|
+
- !binary |-
|
174
|
+
bm8tY2FjaGU=
|
175
|
+
!binary "Y29udGVudC1lbmNvZGluZw==":
|
176
|
+
- !binary |-
|
177
|
+
Z3ppcA==
|
178
|
+
!binary "Y29udGVudC10eXBl":
|
179
|
+
- !binary |-
|
180
|
+
dGV4dC9odG1sOyBjaGFyc2V0PVVURi04
|
181
|
+
!binary "ZGF0ZQ==":
|
182
|
+
- !binary |-
|
183
|
+
TW9uLCAwMyBEZWMgMjAxMiAwODoyMjo0OSBHTVQ=
|
184
|
+
!binary "ZXRhZw==":
|
185
|
+
- !binary |-
|
186
|
+
ImM3MmNjZDI2YzEzNDc2ZDMyNGQ4ZjAwMWJmNjRlYWI2NDA1MDM3OTgi
|
187
|
+
!binary "ZXhwaXJlcw==":
|
188
|
+
- !binary |-
|
189
|
+
TW9uLCAwMyBEZWMgMjAxMiAwODoyMjo0OCBHTVQ=
|
190
|
+
!binary "c2VydmVy":
|
191
|
+
- !binary |-
|
192
|
+
VG9ybmFkb1NlcnZlci8yLjM=
|
193
|
+
!binary "Y29udGVudC1sZW5ndGg=":
|
194
|
+
- !binary |-
|
195
|
+
NTEy
|
196
|
+
!binary "Y29ubmVjdGlvbg==":
|
197
|
+
- !binary |-
|
198
|
+
a2VlcC1hbGl2ZQ==
|
199
|
+
body:
|
200
|
+
encoding: ASCII-8BIT
|
201
|
+
string: !binary |-
|
202
|
+
H4sIAAAAAAAAA7WTTW7CQAyFrxJlnQUzmd8eoJeoUBVoqJBKWiWBTcTd+9mU
|
203
|
+
qYS6QEJdDMRvnj320/NSP/fdfBz7+qla6qE7yEfBmqoejofXr26cJ3CzWoGc
|
204
|
+
+nHafw4CLHWnv9PcKWGpj1P3fqn11Y/bfpi5t6vzmbxrlRfTVKGpUlPlhpIc
|
205
|
+
zyE0xJZL23LALCwL1sJpwVvw1nEiB9yBO3BH7Mj3lgPHk+clhhPAArUCcYQf
|
206
|
+
4UdqRLBIXoSTwBO8RG6Cl8AyWIabwTN4JieTk8nJec04/dBtPvo3nf93ViQ6
|
207
|
+
y+Vu12/n/elWCn+RYnOHau5GNRqhD9qQzhGL5gyYATSghqENuKVxC27BrMQM
|
208
|
+
1EouaS2cltgROykGx4E7cMfQnlxPXS+vgHvyA1gAC/ADeIAbBIcfqRPBI1ik
|
209
|
+
boKX4CRyE1iCl/nP3CHM+hHV1EI/XjUq341ZTU35f3Cr6IQUTMyQzCSjcBhV
|
210
|
+
ZQdSv8IpnoWjvoWjXiUufhXpKaaSg6vs8Ip35S1eEumLh3lPZFepuVfvSkx+
|
211
|
+
8a9ID6aS862e5Z0cHlIdSe/x6h8bzpwZifhTh9KKOpSWdc0ZSVede3Gprjjf
|
212
|
+
ZcXhi1t1va9OFUnIE2eqI+EXVwrGvay5OhOerjocWfOy3sin7oRX1hyOrjc8
|
213
|
+
cWqWvh926/kbJ9GBE1oFAAA=
|
214
|
+
http_version: !binary |-
|
215
|
+
MS4x
|
216
|
+
recorded_at: Mon, 03 Dec 2012 08:22:49 GMT
|
217
|
+
- request:
|
218
|
+
method: get
|
219
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/user/anon-7728753491-228396/features?anonymous=true
|
220
|
+
body:
|
221
|
+
encoding: US-ASCII
|
222
|
+
string: ''
|
223
|
+
headers:
|
224
|
+
accept:
|
225
|
+
- application/json
|
226
|
+
user-agent:
|
227
|
+
- Ruby
|
228
|
+
authorization:
|
229
|
+
- lnUG0NAuKXwRxzlO0Ogwh3JLRBr3Fq8SBxcNHUJo
|
230
|
+
accept-encoding:
|
231
|
+
- gzip
|
232
|
+
connection:
|
233
|
+
- keep-alive
|
234
|
+
keep-alive:
|
235
|
+
- 30
|
236
|
+
response:
|
237
|
+
status:
|
238
|
+
code: 200
|
239
|
+
message: OK
|
240
|
+
headers:
|
241
|
+
cache-control:
|
242
|
+
- no-cache
|
243
|
+
content-type:
|
244
|
+
- text/html; charset=UTF-8
|
245
|
+
date:
|
246
|
+
- Wed, 05 Dec 2012 14:39:12 GMT
|
247
|
+
etag:
|
248
|
+
- ! '"264d951f33c092a35f602231d4ec8405a26210c3"'
|
249
|
+
expires:
|
250
|
+
- Wed, 05 Dec 2012 14:39:11 GMT
|
251
|
+
server:
|
252
|
+
- TornadoServer/2.3
|
253
|
+
content-length:
|
254
|
+
- '73'
|
255
|
+
connection:
|
256
|
+
- keep-alive
|
257
|
+
body:
|
258
|
+
encoding: US-ASCII
|
259
|
+
string: ! '{"Feature": {"a": true, "b": false}, "Feature1": {"a": true, "b":
|
260
|
+
false}}'
|
261
|
+
http_version: '1.1'
|
262
|
+
recorded_at: Wed, 05 Dec 2012 14:39:10 GMT
|
263
|
+
- request:
|
264
|
+
method: get
|
265
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/user/anon-2185763342-896893/features?anonymous=true
|
266
|
+
body:
|
267
|
+
encoding: US-ASCII
|
268
|
+
string: ''
|
269
|
+
headers:
|
270
|
+
accept:
|
271
|
+
- application/json
|
272
|
+
user-agent:
|
273
|
+
- Ruby
|
274
|
+
authorization:
|
275
|
+
- lnUG0NAuKXwRxzlO0Ogwh3JLRBr3Fq8SBxcNHUJo
|
276
|
+
accept-encoding:
|
277
|
+
- gzip
|
278
|
+
connection:
|
279
|
+
- keep-alive
|
280
|
+
keep-alive:
|
281
|
+
- 30
|
282
|
+
response:
|
283
|
+
status:
|
284
|
+
code: 200
|
285
|
+
message: OK
|
286
|
+
headers:
|
287
|
+
cache-control:
|
288
|
+
- no-cache
|
289
|
+
content-type:
|
290
|
+
- text/html; charset=UTF-8
|
291
|
+
date:
|
292
|
+
- Wed, 05 Dec 2012 14:43:22 GMT
|
293
|
+
etag:
|
294
|
+
- ! '"e4aa2deb75a20bceea04a488c5463fc3b71a1b4d"'
|
295
|
+
expires:
|
296
|
+
- Wed, 05 Dec 2012 14:43:21 GMT
|
297
|
+
server:
|
298
|
+
- TornadoServer/2.3
|
299
|
+
content-length:
|
300
|
+
- '73'
|
301
|
+
connection:
|
302
|
+
- keep-alive
|
303
|
+
body:
|
304
|
+
encoding: US-ASCII
|
305
|
+
string: ! '{"Feature": {"a": true, "b": false}, "Feature1": {"a": false, "b":
|
306
|
+
true}}'
|
307
|
+
http_version: '1.1'
|
308
|
+
recorded_at: Wed, 05 Dec 2012 14:43:20 GMT
|
309
|
+
- request:
|
310
|
+
method: get
|
311
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/user/anon-8966033241-785175/features?anonymous=true
|
312
|
+
body:
|
313
|
+
encoding: US-ASCII
|
314
|
+
string: ''
|
315
|
+
headers:
|
316
|
+
accept:
|
317
|
+
- application/json
|
318
|
+
user-agent:
|
319
|
+
- Ruby
|
320
|
+
authorization:
|
321
|
+
- lnUG0NAuKXwRxzlO0Ogwh3JLRBr3Fq8SBxcNHUJo
|
322
|
+
accept-encoding:
|
323
|
+
- gzip
|
324
|
+
connection:
|
325
|
+
- keep-alive
|
326
|
+
keep-alive:
|
327
|
+
- 30
|
328
|
+
response:
|
329
|
+
status:
|
330
|
+
code: 200
|
331
|
+
message: OK
|
332
|
+
headers:
|
333
|
+
cache-control:
|
334
|
+
- no-cache
|
335
|
+
content-type:
|
336
|
+
- text/html; charset=UTF-8
|
337
|
+
date:
|
338
|
+
- Wed, 05 Dec 2012 14:43:31 GMT
|
339
|
+
etag:
|
340
|
+
- ! '"e4aa2deb75a20bceea04a488c5463fc3b71a1b4d"'
|
341
|
+
expires:
|
342
|
+
- Wed, 05 Dec 2012 14:43:30 GMT
|
343
|
+
server:
|
344
|
+
- TornadoServer/2.3
|
345
|
+
content-length:
|
346
|
+
- '73'
|
347
|
+
connection:
|
348
|
+
- keep-alive
|
349
|
+
body:
|
350
|
+
encoding: US-ASCII
|
351
|
+
string: ! '{"Feature": {"a": true, "b": false}, "Feature1": {"a": false, "b":
|
352
|
+
true}}'
|
353
|
+
http_version: '1.1'
|
354
|
+
recorded_at: Wed, 05 Dec 2012 14:43:29 GMT
|
355
|
+
- request:
|
356
|
+
method: get
|
357
|
+
uri: https://www.fluidfeatures.com/service/app/1vu33ki6emqe3/user/anon-3787193327-752927/features?anonymous=true
|
358
|
+
body:
|
359
|
+
encoding: US-ASCII
|
360
|
+
string: ''
|
361
|
+
headers:
|
362
|
+
accept:
|
363
|
+
- application/json
|
364
|
+
user-agent:
|
365
|
+
- Ruby
|
366
|
+
authorization:
|
367
|
+
- lnUG0NAuKXwRxzlO0Ogwh3JLRBr3Fq8SBxcNHUJo
|
368
|
+
accept-encoding:
|
369
|
+
- gzip
|
370
|
+
connection:
|
371
|
+
- keep-alive
|
372
|
+
keep-alive:
|
373
|
+
- 30
|
374
|
+
response:
|
375
|
+
status:
|
376
|
+
code: 200
|
377
|
+
message: OK
|
378
|
+
headers:
|
379
|
+
cache-control:
|
380
|
+
- no-cache
|
381
|
+
content-type:
|
382
|
+
- text/html; charset=UTF-8
|
383
|
+
date:
|
384
|
+
- Wed, 05 Dec 2012 14:44:19 GMT
|
385
|
+
etag:
|
386
|
+
- ! '"264d951f33c092a35f602231d4ec8405a26210c3"'
|
387
|
+
expires:
|
388
|
+
- Wed, 05 Dec 2012 14:44:18 GMT
|
389
|
+
server:
|
390
|
+
- TornadoServer/2.3
|
391
|
+
content-length:
|
392
|
+
- '73'
|
393
|
+
connection:
|
394
|
+
- keep-alive
|
395
|
+
body:
|
396
|
+
encoding: US-ASCII
|
397
|
+
string: ! '{"Feature": {"a": true, "b": false}, "Feature1": {"a": true, "b":
|
398
|
+
false}}'
|
399
|
+
http_version: '1.1'
|
400
|
+
recorded_at: Wed, 05 Dec 2012 14:44:18 GMT
|
401
|
+
recorded_with: VCR 2.3.0
|