bbc_redux 0.4.0.pre

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,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