fluidfeatures 0.4.1 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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