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.
@@ -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