bbc_redux 0.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ require 'representable'
2
+ require 'representable/json'
3
+ require 'representable/json/collection'
4
+
5
+ require_relative 'channel'
6
+
7
+ module BBC
8
+ module Redux
9
+
10
+ # @private
11
+ module Serializers
12
+
13
+ class Asset < Representable::Decorator
14
+ include Representable::JSON
15
+
16
+ property :description
17
+ property :access_key, :as => :key
18
+ property :name
19
+ property :reference
20
+ property :uuid
21
+
22
+ nested :timing do
23
+ property :duration
24
+ property :start
25
+ end
26
+
27
+ property :channel, :class => BBC::Redux::Channel do
28
+ property :name
29
+ property :display_name
30
+ end
31
+ end
32
+
33
+ class Channel < Representable::Decorator
34
+ include Representable::JSON
35
+ property :category_id, :as => :category
36
+ property :display_name, :as => :longname
37
+ property :name
38
+ property :sortorder
39
+ end
40
+
41
+ class Channels < Representable::Decorator
42
+ include Representable::JSON::Collection
43
+ items extend: Channel, class: BBC::Redux::Channel
44
+ end
45
+
46
+ class ChannelCategory < Representable::Decorator
47
+ include Representable::JSON
48
+ property :description
49
+ property :id
50
+ property :priority
51
+ end
52
+
53
+ class ChannelCategories < Representable::Decorator
54
+ include Representable::JSON::Collection
55
+ items extend: ChannelCategory, class: BBC::Redux::ChannelCategory
56
+ end
57
+
58
+ class SearchResults < Representable::Decorator
59
+ include Representable::JSON
60
+ property :created_at, :as => :time
61
+ property :offset
62
+ property :query
63
+ property :query_time, :as => :elapsed
64
+ property :total, :as => :total_found
65
+ property :total_returned
66
+
67
+ nested :results do
68
+ collection :assets, :extend => Asset, :class => BBC::Redux::Asset
69
+ end
70
+ end
71
+
72
+ class User < Representable::Decorator
73
+ include Representable::JSON
74
+
75
+ property :can_invite
76
+ property :created
77
+ property :id
78
+ property :uuid
79
+
80
+ collection :permitted_services
81
+
82
+ nested :details do
83
+ property :department
84
+ property :division
85
+ property :email
86
+ property :name
87
+ property :username
88
+ end
89
+
90
+ nested :legal do
91
+ property :legal_accepted, :as => :accepted
92
+ property :legal_html
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
101
+
@@ -0,0 +1,107 @@
1
+ require 'virtus'
2
+
3
+ module BBC
4
+ module Redux
5
+
6
+ # Redux API User Object
7
+ #
8
+ # @example Properties of the user object
9
+ #
10
+ # user = redux_client.user
11
+ #
12
+ # user.can_invite? #=> Boolean
13
+ # user.created #=> DateTime
14
+ # user.email #=> String
15
+ # user.first_name #=> String
16
+ # user.id #=> Integer
17
+ # user.last_name #=> String
18
+ # user.username #=> String
19
+ # user.uuid #=> String
20
+ #
21
+ # @example Check if user can proceed or needs to sign T&C's
22
+ #
23
+ # if user.must_sign_terms?
24
+ # puts "Hey #{user.name}, you need to sign these terms"
25
+ # puts user.legal_html
26
+ # end
27
+ #
28
+ # @author Matt Haynes <matt.haynes@bbc.co.uk>
29
+ class User
30
+
31
+ include Virtus.value_object
32
+
33
+ # @!attribute [r] can_invite
34
+ # @return [Boolean] whether the user has ability to invite others to Redux
35
+ attribute :can_invite, Boolean
36
+
37
+ alias :can_invite? :can_invite
38
+
39
+ # @!attribute [r] created
40
+ # @return [DateTime] the date on which user account was created
41
+ attribute :created, DateTime
42
+
43
+ # @!attribute [r] department
44
+ # @return [String] the user's organisational department
45
+ attribute :department, String, :default => ''
46
+
47
+ # @!attribute [r] division
48
+ # @return [String] the user's organisational division
49
+ attribute :division, String, :default => ''
50
+
51
+ # @!attribute [r] email
52
+ # @return [String] the user's email
53
+ attribute :email, String, :default => ''
54
+
55
+ # @!attribute [r] id
56
+ # @return [Integer] the user's id
57
+ attribute :id, Integer
58
+
59
+ # @!attribute [r] legal_accepted
60
+ # @see User#must_sign_terms?
61
+ # @return [String] whether the user has accepted the T&C's
62
+ attribute :legal_accepted, Boolean, :default => true
63
+
64
+ # @!attribute [r] legal_html
65
+ # @return [String] a blob of T&C's HTML that the user may need to accept
66
+ attribute :legal_html, String, :default => ''
67
+
68
+ # @!attribute [r] name
69
+ # @return [String] the user's full name
70
+ attribute :name, String, :default => ''
71
+
72
+ # @!attribute [r] permitted_services
73
+ # @return [Array<String>] a list of services user has access to
74
+ attribute :permitted_services, Array[String], :default => []
75
+
76
+ # @!attribute [r] username
77
+ # @return [String] the user's username
78
+ attribute :username, String, :default => ''
79
+
80
+ # @!attribute [r] uuid
81
+ # @return [String] the user's uuid
82
+ attribute :uuid, String
83
+
84
+ # @!attribute [r] first_name
85
+ # @return [String] the user's first name
86
+ def first_name
87
+ name.split(' ').first
88
+ end
89
+
90
+ # @!attribute [r] last_name
91
+ # @return [String] the user's last name
92
+ def last_name
93
+ name.split(' ').last
94
+ end
95
+
96
+ # @!attribute [r] must_sign_terms?
97
+ # @see User#legal_accepted
98
+ # @see User#legal_html
99
+ # @return [String] whether the user must sign some T&C's before proceeding
100
+ def must_sign_terms?
101
+ !legal_accepted
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,8 @@
1
+ module BBC
2
+ module Redux
3
+
4
+ # Library version
5
+ VERSION = '0.4.0.pre'
6
+
7
+ end
8
+ end
data/lib/bbc/redux.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative 'redux/asset'
2
+ require_relative 'redux/channel'
3
+ require_relative 'redux/channel_category'
4
+ require_relative 'redux/client'
5
+ require_relative 'redux/end_points'
6
+ require_relative 'redux/key'
7
+ require_relative 'redux/media_url'
8
+ require_relative 'redux/search_results'
9
+ require_relative 'redux/serializers'
10
+ require_relative 'redux/user'
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe BBC::Redux::Asset do
4
+
5
+ let(:json) { read_fixture 'asset.json' }
6
+
7
+ let(:asset) { described_class.new }
8
+
9
+ let(:mapper) { BBC::Redux::Serializers::Asset }
10
+
11
+ subject { mapper.new(asset).from_json(json) }
12
+
13
+ {
14
+
15
+ # attribute => expected_value
16
+ 'channel.class' => BBC::Redux::Channel,
17
+ 'channel.display_name' => 'CBeebies',
18
+ 'channel.name' => 'cbeebies',
19
+ :access_key => '1-1397227462-ba632af7af1ad',
20
+ :description => 'Animated adventures of Pingu. [S]',
21
+ :disk_reference => '5966413090093319525',
22
+ :duration => 300,
23
+ :key => BBC::Redux::Key.new('1-1397227462-ba632af7af1ad'),
24
+ :name => 'Pingu',
25
+ :reference => '5966413090093319525',
26
+ :start => DateTime.parse('2014-01-08 06:50:00 +0000'),
27
+ :title => 'Pingu',
28
+ :uuid => '26a141fc-8511-4fef-aa2b-af1d1de5a75a',
29
+
30
+ }.each_pair do |attribute, value|
31
+ its(attribute) { should eq(value) }
32
+ end
33
+
34
+ BBC::Redux::MediaUrl::TEMPLATES.each do |type|
35
+ describe "##{type}_url" do
36
+ it 'returns url of the correct type using the asset reference and key' do
37
+ url = subject.send(:"#{type}_url")
38
+ expect(url.type).to eq(type)
39
+ expect(url.identifier).to eq(subject.reference)
40
+ expect(url.key).to eq(subject.key)
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe BBC::Redux::ChannelCategory do
4
+
5
+ let(:json) { read_fixture 'channel_categories.json' }
6
+
7
+ let(:mapper) { BBC::Redux::Serializers::ChannelCategories }
8
+
9
+ subject { mapper.new([]).from_json(json) }
10
+
11
+ {
12
+
13
+ # attr / method => expected_value
14
+ 'size' => 5,
15
+ 'first.description' => 'BBC TV',
16
+ 'first.id' => 1,
17
+ 'first.priority' => 1000,
18
+
19
+ }.each_pair do |attribute, value|
20
+ its(attribute) { should eq(value) }
21
+ end
22
+
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe BBC::Redux::Channel do
4
+
5
+ let(:json) { read_fixture 'channels.json' }
6
+
7
+ let(:mapper) { BBC::Redux::Serializers::Channels }
8
+
9
+ subject { mapper.new([]).from_json(json) }
10
+
11
+ {
12
+
13
+ # attr / method => expected_value
14
+ 'size' => 8,
15
+ 'first.name' => 'bbcone',
16
+ 'first.display_name' => 'BBC One',
17
+ 'first.category_id' => 1,
18
+ 'first.sort_order' => 1010,
19
+
20
+ }.each_pair do |attribute, value|
21
+ its(attribute) { should eq(value) }
22
+ end
23
+
24
+ end
@@ -0,0 +1,279 @@
1
+ require 'spec_helper'
2
+
3
+ describe BBC::Redux::Client do
4
+
5
+ let(:http_client) { double('http_client') }
6
+ let(:token) { 'SOME-TOKEN' }
7
+ let(:instance) {
8
+ described_class.new(:token => token, :http => http_client)
9
+ }
10
+
11
+ shared_examples_for 'a json based http method' do
12
+ let(:unknown_response) { double('http_resp', :code => rand(200..399)) }
13
+
14
+ let(:badjson_exception) { BBC::Redux::Client::JsonParseException }
15
+ let(:badjson_response) { double('http_resp', :code => 200, :body => '') }
16
+
17
+ let(:fivexx_exception) { BBC::Redux::Client::HttpException }
18
+ let(:fivexx_response) { double('http_resp', :code => rand(500..505)) }
19
+
20
+ let(:forbidden_exception) { BBC::Redux::Client::ForbiddenException }
21
+ let(:forbidden_response) { double('http_resp', :code => 403) }
22
+
23
+ it 'should raise forbidden exception when HTTP API returns 403' do
24
+ expect(http_client).to receive(:post).and_return(forbidden_response)
25
+ expect { subject }.to raise_exception(forbidden_exception)
26
+ end
27
+
28
+ it 'should raise and http exception when HTTP API returns 5XX' do
29
+ expect(http_client).to receive(:post).and_return(fivexx_response)
30
+ expect { subject }.to raise_exception(fivexx_exception)
31
+ end
32
+
33
+ it 'should raise a generic exception with an unknown http status' do
34
+ expect(http_client).to receive(:post).and_return(unknown_response)
35
+ expect { subject }.to raise_exception
36
+ end
37
+
38
+ it 'should raise a json parse error when backend returns junk' do
39
+ expect(http_client).to receive(:post).and_return(badjson_response)
40
+ expect { subject }.to raise_exception(badjson_exception)
41
+ end
42
+ end
43
+
44
+ context 'initialized with username / password' do
45
+ subject { described_class.new({
46
+ :http => http_client, :username => 'foo', :password => 'bar'
47
+ }) }
48
+
49
+ it_behaves_like 'a json based http method'
50
+
51
+ it 'connects to redux api to get token' do
52
+ resp = double(:resp, :code => 200, :body => '{"token":"TOKEN"}')
53
+
54
+ expect(http_client).to \
55
+ receive(:post).with('https://i.bbcredux.com/user/login', {
56
+ :body => {
57
+ :username => 'foo',
58
+ :password => 'bar',
59
+ :token => nil,
60
+ },
61
+ :followlocation => true
62
+ }).and_return(resp)
63
+
64
+ expect(subject.token).to eq("TOKEN")
65
+ end
66
+ end
67
+
68
+ context 'initialized with token' do
69
+ it 'should be ready to go' do
70
+ expect(instance.token).to eq(token)
71
+ expect(instance.http).to eq(http_client)
72
+ end
73
+ end
74
+
75
+ context 'initialized with neither token or username / password' do
76
+ it 'raises an error' do
77
+ expect { described_class.new }.to raise_error
78
+ end
79
+ end
80
+
81
+ describe '#asset' do
82
+ subject { instance.asset(reference) }
83
+
84
+ let(:resp) {
85
+ double(:http_resp, :code => 200, :body => read_fixture('asset.json'))
86
+ }
87
+
88
+ let(:reference) { '5966413090093319525' }
89
+ let(:uuid) { '26a141fc-8511-4fef-aa2b-af1d1de5a75a' }
90
+
91
+ it_behaves_like 'a json based http method'
92
+
93
+ it 'takes json from the backend HTTP API and generates an asset object' do
94
+ expect(http_client).to \
95
+ receive(:post).with('https://i.bbcredux.com/asset/details', {
96
+ :body => { :reference => reference, :token => token },
97
+ :followlocation => true,
98
+ }).and_return(resp)
99
+
100
+ expect(subject.class).to eq(BBC::Redux::Asset)
101
+ expect(subject.name).to eq('Pingu')
102
+ expect(subject.channel.name).to eq('cbeebies')
103
+ end
104
+
105
+ it 'works when given a UUID rather than a disk reference' do
106
+ expect(http_client).to \
107
+ receive(:post).with('https://i.bbcredux.com/asset/details', {
108
+ :body => { :uuid => uuid, :token => token },
109
+ :followlocation => true,
110
+ }).and_return(resp)
111
+
112
+ asset = instance.asset(uuid)
113
+
114
+ expect(asset.class).to eq(BBC::Redux::Asset)
115
+ expect(asset.name).to eq('Pingu')
116
+ expect(asset.channel.name).to eq('cbeebies')
117
+ end
118
+ end
119
+
120
+ describe '#channels' do
121
+ subject { instance.channels }
122
+
123
+ let(:resp) {
124
+ double(:http_resp, :code => 200, :body => read_fixture('channels.json'))
125
+ }
126
+
127
+ it_behaves_like 'a json based http method'
128
+
129
+ it 'takes json from the backend HTTP API and generates list of channels' do
130
+ expect(http_client).to \
131
+ receive(:post).with('https://i.bbcredux.com/asset/channel/available', {
132
+ :body => { :token => token },
133
+ :followlocation => true,
134
+ }).and_return(resp)
135
+
136
+ expect(subject.size).to eq(8)
137
+ expect(subject.first.class).to eq(BBC::Redux::Channel)
138
+ expect(subject.first.name).to eq('bbcone')
139
+ end
140
+
141
+ end
142
+
143
+ describe '#channel_categories' do
144
+ subject { instance.channel_categories }
145
+
146
+ let(:resp) {
147
+ double(:http_resp, :code => 200,
148
+ :body => read_fixture('channel_categories.json')) }
149
+
150
+ it_behaves_like 'a json based http method'
151
+
152
+ it 'takes json from the backend HTTP API, generates list of categories' do
153
+ expect(http_client).to \
154
+ receive(:post).with('https://i.bbcredux.com/asset/channel/categories', {
155
+ :body => { :token => token },
156
+ :followlocation => true,
157
+ }).and_return(resp)
158
+
159
+ expect(subject.size).to eq(5)
160
+ expect(subject.first.class).to eq(BBC::Redux::ChannelCategory)
161
+ expect(subject.first.description).to eq('BBC TV')
162
+ end
163
+
164
+ end
165
+
166
+ describe '#logout' do
167
+ subject { instance.logout }
168
+
169
+ let(:resp) { double(:http_resp, :code => 200, :body => '{}') }
170
+
171
+ it_behaves_like 'a json based http method'
172
+
173
+ it 'posts to logout endpoint' do
174
+ expect(http_client).to \
175
+ receive(:post).with('https://i.bbcredux.com/user/logout', {
176
+ :body => { :token => token },
177
+ :followlocation => true,
178
+ }).and_return(resp)
179
+
180
+ expect(subject).to eq(nil)
181
+ end
182
+
183
+ end
184
+
185
+ describe '#search' do
186
+ subject { instance.search }
187
+
188
+ let(:resp) {
189
+ double(:resp, :code => 200, :body => read_fixture('search_results.json'))
190
+ }
191
+ it_behaves_like 'a json based http method'
192
+
193
+ it 'passes params to backend HTTP API and generates results object' do
194
+ expect(http_client).to \
195
+ receive(:post).with('https://i.bbcredux.com/asset/search', {
196
+ :body => { :q => 'foo', :longer => '200', :token => token },
197
+ :followlocation => true,
198
+ }).and_return(resp)
199
+
200
+ results = instance.search(:q => 'foo', :longer => 200)
201
+
202
+ expect(results.class).to be(BBC::Redux::SearchResults)
203
+ expect(results.query).to eq(:q => 'foo', :longer => 200)
204
+ end
205
+
206
+ context 'channel object based parameters' do
207
+
208
+ let(:bbcone) { BBC::Redux::Channel.new(:name => 'bbcone') }
209
+
210
+ it 'formats them correctly' do
211
+ expect(http_client).to \
212
+ receive(:post).with('https://i.bbcredux.com/asset/search', {
213
+ :body => { :channel => 'bbcone', :token => token },
214
+ :followlocation => true,
215
+ }).and_return(resp)
216
+
217
+ results = instance.search(:channel => bbcone)
218
+
219
+ expect(results.class).to be(BBC::Redux::SearchResults)
220
+ end
221
+
222
+ it 'handles an array correctly' do
223
+ url = 'https://i.bbcredux.com/asset/search?channel=bbcone&channel=bbctwo'
224
+
225
+ expect(http_client).to \
226
+ receive(:post).with(url, {
227
+ :body => { :token => token },
228
+ :followlocation => true,
229
+ }).and_return(resp)
230
+
231
+ results = instance.search(:channel => [ bbcone, 'bbctwo' ])
232
+
233
+ expect(results.class).to be(BBC::Redux::SearchResults)
234
+ end
235
+ end
236
+
237
+ context 'date / time based parameters' do
238
+ it 'formats them correctly' do
239
+ [ Time.now, Date.today, DateTime.now ].each do |datey|
240
+ formatted = datey.strftime('%Y-%m-%dT%H:%M:%S')
241
+
242
+ expect(http_client).to \
243
+ receive(:post).with('https://i.bbcredux.com/asset/search', {
244
+ :body => { :date => formatted, :token => token },
245
+ :followlocation => true,
246
+ }).and_return(resp)
247
+
248
+ results = instance.search(:date => datey)
249
+
250
+ expect(results.class).to be(BBC::Redux::SearchResults)
251
+ end
252
+ end
253
+ end
254
+
255
+ end
256
+
257
+ describe '#user' do
258
+ subject { instance.user }
259
+
260
+ let(:resp) {
261
+ double(:http_resp, :code => 200, :body => read_fixture('user.json'))
262
+ }
263
+
264
+ it_behaves_like 'a json based http method'
265
+
266
+ it 'takes json from the backend HTTP API and generates a user object' do
267
+ expect(http_client).to \
268
+ receive(:post).with('https://i.bbcredux.com/user/details', {
269
+ :body => { :token => token },
270
+ :followlocation => true,
271
+ }).and_return(resp)
272
+
273
+ expect(subject.class).to eq(BBC::Redux::User)
274
+ expect(subject.name).to eq('Jane Smith')
275
+ end
276
+
277
+ end
278
+
279
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe BBC::Redux::Key do
4
+
5
+ let(:value) { '1-1397227462-ba632af7af1adb6f96dbeceb83331ad6' }
6
+
7
+ subject { described_class.new(value) }
8
+
9
+ {
10
+
11
+ # attr/method => expected_value
12
+ :value => '1-1397227462-ba632af7af1adb6f96dbeceb83331ad6',
13
+ :to_s => '1-1397227462-ba632af7af1adb6f96dbeceb83331ad6',
14
+ :expires_at => DateTime.parse('2014-04-11 15:44:22 +0100'),
15
+
16
+ }.each_pair do |attribute, value|
17
+ its(attribute) { should eq(value) }
18
+ end
19
+
20
+ describe '#expired?' do
21
+ it 'should be true when expires_at is in the past' do
22
+ expect(generate_key(Time.now - 300).expired?).to be(true)
23
+ end
24
+
25
+ it 'should be false when expires_at is in the future' do
26
+ expect(generate_key(Time.now + 300).expired?).to be(false)
27
+ end
28
+ end
29
+
30
+ describe '#live?' do
31
+ it 'should be false when expires_at is in the past' do
32
+ expect(generate_key(Time.now - 300).live?).to be(false)
33
+ end
34
+
35
+ it 'should be true when expires_at is in the future' do
36
+ expect(generate_key(Time.now + 300).live?).to be(true)
37
+ end
38
+ end
39
+
40
+ describe '#ttl' do
41
+ it 'should return the key\'s TLL in seconds' do
42
+ expect(generate_key.ttl).to be_within(1).of(0)
43
+ end
44
+ end
45
+
46
+ describe '#==' do
47
+ it 'should be true if keys have same values' do
48
+ expect(subject).to eq(described_class.new(value))
49
+ end
50
+
51
+ it 'should be false if keys have different values' do
52
+ expect(subject).to_not eq(generate_key)
53
+ end
54
+
55
+ it 'should be false if other key is something else' do
56
+ expect(subject).to_not eq(:something_else)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def generate_key(expires_at = Time.now)
63
+ described_class.new "1-#{expires_at.to_i}-ba632af7af1adb6f96dbeceb83331ad6"
64
+ end
65
+
66
+ end