bigbluebutton_rails 0.0.6 → 0.1.0
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 +1 -1
- data/CHANGELOG.rdoc +8 -0
- data/Gemfile +13 -7
- data/Gemfile.lock +112 -86
- data/README.rdoc +26 -80
- data/Rakefile +2 -1
- data/TODO_08 +13 -0
- data/app/controllers/bigbluebutton/rooms_controller.rb +9 -9
- data/app/controllers/bigbluebutton/servers_controller.rb +1 -1
- data/app/models/bigbluebutton_room.rb +16 -14
- data/app/models/bigbluebutton_server.rb +1 -1
- data/app/views/bigbluebutton/rooms/_form.html.erb +1 -1
- data/app/views/bigbluebutton/rooms/external.html.erb +3 -3
- data/app/views/bigbluebutton/rooms/invite.html.erb +3 -3
- data/app/views/bigbluebutton/servers/_form.html.erb +1 -1
- data/bigbluebutton_rails.gemspec +2 -2
- data/lib/bigbluebutton_rails/version.rb +1 -1
- data/spec/controllers/bigbluebutton/rooms_controller_spec.rb +42 -37
- data/spec/factories/bigbluebutton_room.rb +3 -1
- data/spec/models/bigbluebutton_room_db_spec.rb +34 -0
- data/spec/models/bigbluebutton_room_spec.rb +452 -457
- data/spec/models/bigbluebutton_server_db_spec.rb +14 -0
- data/spec/models/bigbluebutton_server_spec.rb +162 -176
- data/spec/rails_app/.gitignore +2 -1
- data/spec/rails_app/db/seeds.rb +16 -4
- data/spec/rails_app/features/activity_monitor_servers.feature +53 -0
- data/spec/rails_app/features/config.yml.example +13 -0
- data/spec/rails_app/features/create_rooms.feature +17 -0
- data/spec/rails_app/features/create_servers.feature +17 -0
- data/spec/rails_app/features/destroy_rooms.feature +12 -0
- data/spec/rails_app/features/destroy_servers.feature +11 -0
- data/spec/rails_app/features/edit_rooms.feature +26 -0
- data/spec/rails_app/features/edit_servers.feature +24 -0
- data/spec/rails_app/features/join_external_rooms.feature +61 -0
- data/spec/rails_app/features/join_mobile.feature +10 -0
- data/spec/rails_app/features/join_rooms.feature +117 -0
- data/spec/rails_app/features/list_and_show_rooms.feature +18 -0
- data/spec/rails_app/features/list_and_show_servers.feature +16 -0
- data/spec/rails_app/features/step_definitions/activity_monitor_servers_step.rb +102 -0
- data/spec/rails_app/features/step_definitions/common_steps.rb +99 -3
- data/spec/rails_app/features/step_definitions/create_rooms_steps.rb +38 -0
- data/spec/rails_app/features/step_definitions/{bigbluebutton_server_steps.rb → create_servers_steps.rb} +11 -6
- data/spec/rails_app/features/step_definitions/destroy_rooms_steps.rb +17 -0
- data/spec/rails_app/features/step_definitions/destroy_servers_steps.rb +15 -0
- data/spec/rails_app/features/step_definitions/edit_rooms_steps.rb +15 -0
- data/spec/rails_app/features/step_definitions/edit_servers_steps.rb +15 -0
- data/spec/rails_app/features/step_definitions/join_mobile_steps.rb +5 -0
- data/spec/rails_app/features/step_definitions/join_rooms_steps.rb +49 -0
- data/spec/rails_app/features/step_definitions/list_and_show_rooms_steps.rb +11 -0
- data/spec/rails_app/features/step_definitions/list_and_show_servers_steps.rb +11 -0
- data/spec/rails_app/features/support/configurations.rb +34 -0
- data/spec/rails_app/features/support/content.rb +27 -7
- data/spec/rails_app/features/support/env_custom.rb +21 -0
- data/spec/rails_app/features/support/factories/bigbluebutton_server_integration.rb +5 -0
- data/spec/rails_app/features/support/hooks.rb +14 -0
- data/spec/rails_app/features/support/locales.rb +18 -0
- data/spec/rails_app/features/support/paths.rb +25 -10
- data/spec/rails_app/features/support/selectors.rb +26 -0
- data/spec/rails_app/features/support/templates.rb +241 -0
- metadata +43 -20
- data/spec/factories/integration/bigbluebutton_server_integration.rb +0 -8
- data/spec/integration_conf.yml.example +0 -5
- data/spec/rails_app/features/join_external_bigbluebutton_rooms.feature +0 -19
- data/spec/rails_app/features/manage_bigbluebutton_rooms.feature +0 -9
- data/spec/rails_app/features/manage_bigbluebutton_servers.feature +0 -9
- data/spec/rails_app/features/step_definitions/bigbluebutton_room_steps.rb +0 -64
- data/spec/rails_app/features/support/env_gem.rb +0 -10
- data/spec/rails_app/features/support/forms.rb +0 -12
- data/spec/support/integration/integration_conf.rb +0 -16
data/Rakefile
CHANGED
@@ -56,8 +56,9 @@ end
|
|
56
56
|
task :cucumber do
|
57
57
|
if File.exists? "features/"
|
58
58
|
puts "* Gem features"
|
59
|
-
sh
|
59
|
+
sh "cucumber features/"
|
60
60
|
end
|
61
|
+
|
61
62
|
puts "* Dummy app features"
|
62
63
|
cd File.join(File.dirname(__FILE__), "spec", "rails_app")
|
63
64
|
sh "cucumber features/"
|
data/TODO_08
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
TODO for BigBlueButton 0.8:
|
2
|
+
|
3
|
+
See:
|
4
|
+
* http://code.google.com/p/bigbluebutton/wiki/API#Version_0.8
|
5
|
+
* http://groups.google.com/group/bigbluebutton-dev/browse_thread/thread/c214cbe9bdb2268a?pli=1
|
6
|
+
|
7
|
+
TODO:
|
8
|
+
* Support for recordings
|
9
|
+
* Keep track of older meetings?
|
10
|
+
* Pre-upload of slides
|
11
|
+
* New arguments in some methods (e.g. userID, record, duration, meta_*)
|
12
|
+
* New return values (e.g createTime).
|
13
|
+
* We don't need to randomize the meeting_id anymore (to confirm)
|
@@ -165,7 +165,7 @@ class Bigbluebutton::RoomsController < ApplicationController
|
|
165
165
|
role = @room.user_role(params[:user])
|
166
166
|
end
|
167
167
|
|
168
|
-
unless role.nil? or name.nil?
|
168
|
+
unless role.nil? or name.nil? or name.empty?
|
169
169
|
join_internal(name, role, :invite)
|
170
170
|
else
|
171
171
|
flash[:error] = t('bigbluebutton_rails.rooms.errors.auth.failure')
|
@@ -185,11 +185,11 @@ class Bigbluebutton::RoomsController < ApplicationController
|
|
185
185
|
# Authenticates an user using name and password passed in the params from #external
|
186
186
|
# Uses params[:meeting] to get the meetingID of the target room
|
187
187
|
def external_auth
|
188
|
-
@room = nil
|
189
188
|
if !params[:meeting].blank? && !params[:user].blank?
|
190
189
|
@server.fetch_meetings
|
191
190
|
@room = @server.meetings.select{ |r| r.meetingid == params[:meeting] }.first
|
192
191
|
else
|
192
|
+
@room = nil
|
193
193
|
message = t('bigbluebutton_rails.rooms.errors.auth.wrong_params')
|
194
194
|
redirect_to request.referer, :notice => message
|
195
195
|
return
|
@@ -202,11 +202,11 @@ class Bigbluebutton::RoomsController < ApplicationController
|
|
202
202
|
name = bigbluebutton_user.nil? ? params[:user][:name] : bigbluebutton_user.name
|
203
203
|
role = @room.user_role(params[:user])
|
204
204
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
redirect_to(
|
205
|
+
# FIXME: use internal_join ?
|
206
|
+
unless role.nil? or name.nil? or name.empty?
|
207
|
+
url = @room.perform_join(name, role, request)
|
208
|
+
unless url.nil?
|
209
|
+
redirect_to(url)
|
210
210
|
else
|
211
211
|
flash[:error] = t('bigbluebutton_rails.rooms.errors.auth.not_running')
|
212
212
|
render :external
|
@@ -264,13 +264,13 @@ class Bigbluebutton::RoomsController < ApplicationController
|
|
264
264
|
end
|
265
265
|
|
266
266
|
def join_mobile
|
267
|
-
@join_url =
|
267
|
+
@join_url = join_bigbluebutton_server_room_url(@server, @room, :mobile => '1')
|
268
268
|
@join_url.gsub!(/http:\/\//i, "bigbluebutton://")
|
269
269
|
|
270
270
|
# TODO: we can't use the mconf url because the mobile client scanning the qrcode is not
|
271
271
|
# logged. so we are using the full BBB url for now.
|
272
272
|
@qrcode_url = @room.join_url(bigbluebutton_user.name, bigbluebutton_role(@room))
|
273
|
-
@qrcode_url.gsub!(
|
273
|
+
@qrcode_url.gsub!(/http:\/\//i, "bigbluebutton://")
|
274
274
|
end
|
275
275
|
|
276
276
|
protected
|
@@ -34,7 +34,7 @@ class Bigbluebutton::ServersController < ApplicationController
|
|
34
34
|
|
35
35
|
# update_list works only for html
|
36
36
|
if params[:update_list] && (params[:format].nil? || params[:format].to_s == "html")
|
37
|
-
render :
|
37
|
+
render :partial => 'activity_list', :locals => { :server => @server }
|
38
38
|
return
|
39
39
|
end
|
40
40
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/secure_random'
|
2
|
-
|
3
1
|
class BigbluebuttonRoom < ActiveRecord::Base
|
4
2
|
belongs_to :server, :class_name => 'BigbluebuttonServer'
|
5
3
|
belongs_to :owner, :polymorphic => true
|
@@ -41,6 +39,9 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
41
39
|
after_initialize :init
|
42
40
|
before_validation :set_param
|
43
41
|
|
42
|
+
# the full logout_url used when logout_url is a relative path
|
43
|
+
attr_accessor :full_logout_url
|
44
|
+
|
44
45
|
# Convenience method to access the attribute <tt>running</tt>
|
45
46
|
def is_running?
|
46
47
|
@running
|
@@ -201,10 +202,9 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
201
202
|
def perform_join(username, role, request=nil)
|
202
203
|
fetch_is_running?
|
203
204
|
|
204
|
-
# if the
|
205
|
-
# and join it
|
205
|
+
# if the user is a moderator, create the room (if needed) and join it
|
206
206
|
if role == :moderator
|
207
|
-
add_domain_to_logout_url(request.protocol, request.
|
207
|
+
add_domain_to_logout_url(request.protocol, request.host_with_port) unless request.nil?
|
208
208
|
send_create unless is_running?
|
209
209
|
ret = join_url(username, role)
|
210
210
|
|
@@ -218,6 +218,7 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
218
218
|
end
|
219
219
|
|
220
220
|
# add a domain name and/or protocol to the logout_url if needed
|
221
|
+
# it doesn't save in the db, just updates the instance
|
221
222
|
def add_domain_to_logout_url(protocol, host)
|
222
223
|
unless logout_url.nil?
|
223
224
|
url = logout_url.downcase
|
@@ -227,7 +228,7 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
227
228
|
end
|
228
229
|
url = protocol + url
|
229
230
|
end
|
230
|
-
|
231
|
+
self.full_logout_url = url.downcase
|
231
232
|
end
|
232
233
|
end
|
233
234
|
|
@@ -248,29 +249,30 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
248
249
|
end
|
249
250
|
|
250
251
|
def random_meetingid
|
251
|
-
#ActiveSupport::SecureRandom.hex(16)
|
252
252
|
# TODO temporarily using the name to get a friendlier meetingid
|
253
253
|
if self[:name].blank?
|
254
|
-
|
254
|
+
SecureRandom.hex(8)
|
255
255
|
else
|
256
|
-
self[:name] + '-' +
|
256
|
+
self[:name] + '-' + SecureRandom.random_number(9999).to_s
|
257
257
|
end
|
258
258
|
end
|
259
259
|
|
260
260
|
def random_voice_bridge
|
261
|
-
value = (70000 +
|
261
|
+
value = (70000 + SecureRandom.random_number(9999)).to_s
|
262
262
|
count = 1
|
263
263
|
while not BigbluebuttonRoom.find_by_voice_bridge(value).nil? and count < 10
|
264
264
|
count += 1
|
265
|
-
value = (70000 +
|
265
|
+
value = (70000 + SecureRandom.random_number(9999)).to_s
|
266
266
|
end
|
267
267
|
value
|
268
268
|
end
|
269
269
|
|
270
270
|
def do_create_meeting
|
271
|
-
|
272
|
-
|
273
|
-
|
271
|
+
opts = { :moderatorPW => self.moderator_password, :attendeePW => self.attendee_password,
|
272
|
+
:welcome => self.welcome_msg, :dialNumber => self.dial_number,
|
273
|
+
:logoutURL => self.full_logout_url || self.logout_url,
|
274
|
+
:maxParticipants => self.max_participants, :voiceBridge => self.voice_bridge }
|
275
|
+
self.server.api.create_meeting(self.name, self.meetingid, opts)
|
274
276
|
end
|
275
277
|
|
276
278
|
# if :param wasn't set, sets it as :name downcase and parameterized
|
@@ -7,7 +7,7 @@
|
|
7
7
|
<%= form_for [@server, @room], :url => url do |f| %>
|
8
8
|
<% if @room.errors.any? %>
|
9
9
|
<div id="error_explanation">
|
10
|
-
<h2><%= pluralize(@room.errors.count, "error")
|
10
|
+
<h2><%= pluralize(@room.errors.count, "error") %>:</h2>
|
11
11
|
<ul>
|
12
12
|
<% @room.errors.full_messages.each do |msg| %>
|
13
13
|
<li><%= msg %></li>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<h1>Join the meeting <%= @room.name %></h1>
|
2
2
|
|
3
|
-
<
|
3
|
+
<div id="error_explanation"><%= flash[:error] %></div>
|
4
4
|
|
5
5
|
<p>This meeting was created from another application.<br/>You must provide a name and a password to join:</p>
|
6
6
|
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<%= hidden_field_tag "meeting", params[:meeting] %>
|
9
9
|
|
10
10
|
<div class="field">
|
11
|
-
Name
|
11
|
+
<label for="user_name">Name:</label><br />
|
12
12
|
<% if bigbluebutton_user.nil? %>
|
13
13
|
<%= text_field_tag "user[name]", "" %>
|
14
14
|
<% else %>
|
@@ -16,7 +16,7 @@
|
|
16
16
|
<% end %>
|
17
17
|
</div>
|
18
18
|
<div class="field">
|
19
|
-
Password
|
19
|
+
<label for="user_password">Password:</label><br />
|
20
20
|
<%= password_field_tag "user[password]", "" %>
|
21
21
|
</div>
|
22
22
|
|
@@ -28,7 +28,7 @@ $(document).ready(function(){
|
|
28
28
|
|
29
29
|
<h1>Invite to the meeting <%= @room.name %></h1>
|
30
30
|
|
31
|
-
<
|
31
|
+
<div id="error_explanation"><%= flash[:error] %></div>
|
32
32
|
|
33
33
|
<p>Meeting status: <span id="meeting_status">-</span> (<a href="javascript:ajax_request();">refresh</a>)</p>
|
34
34
|
<p><%= @room.name %> requires a name and/or password to join:</p>
|
@@ -36,7 +36,7 @@ $(document).ready(function(){
|
|
36
36
|
<%= form_tag join_bigbluebutton_server_room_path(@server, @room) do %>
|
37
37
|
|
38
38
|
<div class="field">
|
39
|
-
Name
|
39
|
+
<label for="user_name">Name:</label><br />
|
40
40
|
<% if bigbluebutton_user.nil? %>
|
41
41
|
<%= text_field_tag "user[name]", "" %>
|
42
42
|
<% else %>
|
@@ -44,7 +44,7 @@ $(document).ready(function(){
|
|
44
44
|
<% end %>
|
45
45
|
</div>
|
46
46
|
<div class="field">
|
47
|
-
Password
|
47
|
+
<label for="user_password">Password:</label><br />
|
48
48
|
<% if @room.private %>
|
49
49
|
<%= password_field_tag "user[password]", "" %>
|
50
50
|
<% else %>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<%= form_for(@server) do |f| %>
|
2
2
|
<% if @server.errors.any? %>
|
3
3
|
<div id="error_explanation">
|
4
|
-
<h2><%= pluralize(@server.errors.count, "error")
|
4
|
+
<h2><%= pluralize(@server.errors.count, "error") %>:</h2>
|
5
5
|
<ul>
|
6
6
|
<% @server.errors.full_messages.each do |msg| %>
|
7
7
|
<li><%= msg %></li>
|
data/bigbluebutton_rails.gemspec
CHANGED
@@ -15,6 +15,6 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
|
18
|
-
s.add_dependency("rails", ">= 3.0.
|
19
|
-
s.add_dependency("bigbluebutton-api-ruby", "~> 0.0
|
18
|
+
s.add_dependency("rails", ">= 3.0.0")
|
19
|
+
s.add_dependency("bigbluebutton-api-ruby", "~> 0.1.0")
|
20
20
|
end
|
@@ -49,7 +49,7 @@ describe Bigbluebutton::RoomsController do
|
|
49
49
|
mock_server_and_api
|
50
50
|
controller.stub(:bigbluebutton_user) { user }
|
51
51
|
controller.should_receive(:bigbluebutton_role).and_return(:moderator)
|
52
|
-
controller.should_receive(:
|
52
|
+
controller.should_receive(:join_bigbluebutton_server_room_url).
|
53
53
|
with(mocked_server, room, :mobile => '1').
|
54
54
|
and_return("http://test.com/join/url?mobile=1")
|
55
55
|
mocked_api.should_receive(:join_meeting_url).
|
@@ -482,6 +482,14 @@ describe Bigbluebutton::RoomsController do
|
|
482
482
|
it { should set_the_flash.to(I18n.t('bigbluebutton_rails.rooms.errors.auth.failure')) }
|
483
483
|
end
|
484
484
|
|
485
|
+
context "when name is set but empty" do
|
486
|
+
let(:user_hash) { { :password => room.moderator_password, :name => "" } }
|
487
|
+
it { should respond_with(:unauthorized) }
|
488
|
+
it { should assign_to(:room).with(room) }
|
489
|
+
it { should render_template(:invite) }
|
490
|
+
it { should set_the_flash.to(I18n.t('bigbluebutton_rails.rooms.errors.auth.failure')) }
|
491
|
+
end
|
492
|
+
|
485
493
|
context "when the password is wrong" do
|
486
494
|
let(:user_hash) { { :name => "Elftor", :password => nil } }
|
487
495
|
it { should respond_with(:unauthorized) }
|
@@ -534,8 +542,7 @@ describe Bigbluebutton::RoomsController do
|
|
534
542
|
end # #external
|
535
543
|
|
536
544
|
describe "#external_auth" do
|
537
|
-
let(:
|
538
|
-
let(:user_hash) { { :name => "Elftor", :password => new_room.attendee_password } }
|
545
|
+
let(:user_hash) { { :name => "Any Name", :password => new_room.attendee_password } }
|
539
546
|
let(:meetingid) { "my-meeting-id" }
|
540
547
|
let(:new_room) { BigbluebuttonRoom.new(:meetingid => meetingid,
|
541
548
|
:attendee_password => Forgery(:basic).password,
|
@@ -554,7 +561,7 @@ describe Bigbluebutton::RoomsController do
|
|
554
561
|
before { # TODO: this block is being repeated several times, put it in a method or something
|
555
562
|
mocked_server.should_receive(:fetch_meetings)
|
556
563
|
mocked_server.should_receive(:meetings).and_return(meetings)
|
557
|
-
new_room.should_receive(:
|
564
|
+
new_room.should_receive(:perform_join)
|
558
565
|
}
|
559
566
|
before(:each) { post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash }
|
560
567
|
it { should assign_to(:room).with(new_room) }
|
@@ -592,11 +599,6 @@ describe Bigbluebutton::RoomsController do
|
|
592
599
|
}.should raise_error(BigbluebuttonRails::RoomAccessDenied)
|
593
600
|
end
|
594
601
|
|
595
|
-
it "fetches meeting info" do
|
596
|
-
new_room.should_receive(:fetch_meeting_info)
|
597
|
-
post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash
|
598
|
-
end
|
599
|
-
|
600
602
|
context "validates user input and shows error" do
|
601
603
|
before(:each) { post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash }
|
602
604
|
|
@@ -608,6 +610,14 @@ describe Bigbluebutton::RoomsController do
|
|
608
610
|
it { should set_the_flash.to(I18n.t('bigbluebutton_rails.rooms.errors.auth.failure')) }
|
609
611
|
end
|
610
612
|
|
613
|
+
context "when name is empty not set" do
|
614
|
+
let(:user_hash) { { :password => room.moderator_password, :name => "" } }
|
615
|
+
it { should respond_with(:unauthorized) }
|
616
|
+
it { should assign_to(:room).with(new_room) }
|
617
|
+
it { should render_template(:external) }
|
618
|
+
it { should set_the_flash.to(I18n.t('bigbluebutton_rails.rooms.errors.auth.failure')) }
|
619
|
+
end
|
620
|
+
|
611
621
|
context "when the password is wrong" do
|
612
622
|
let(:user_hash) { { :name => "Elftor", :password => nil } }
|
613
623
|
it { should respond_with(:unauthorized) }
|
@@ -617,39 +627,34 @@ describe Bigbluebutton::RoomsController do
|
|
617
627
|
end
|
618
628
|
end
|
619
629
|
|
620
|
-
context "
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
with(new_room.meetingid, user.name, new_room.attendee_password). # here's the validation
|
630
|
-
and_return("http://test.com/attendee/join")
|
631
|
-
post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash
|
630
|
+
context "calls room#perform_join" do
|
631
|
+
context "and redirects to the url received" do
|
632
|
+
before {
|
633
|
+
new_room.should_receive(:perform_join).with(anything, :attendee, request).
|
634
|
+
and_return("http://test.com/attendee/join")
|
635
|
+
}
|
636
|
+
before(:each) { post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash }
|
637
|
+
it { should respond_with(:redirect) }
|
638
|
+
it { should redirect_to("http://test.com/attendee/join") }
|
632
639
|
end
|
633
640
|
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash
|
639
|
-
should respond_with(:
|
640
|
-
should
|
641
|
+
context "and shows error if it returns nil" do
|
642
|
+
before {
|
643
|
+
new_room.should_receive(:perform_join).with(user_hash[:name], :attendee, request).and_return(nil)
|
644
|
+
}
|
645
|
+
before(:each) { post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash }
|
646
|
+
it { should respond_with(:success) }
|
647
|
+
it { should render_template(:external) }
|
648
|
+
it { should set_the_flash.to(I18n.t('bigbluebutton_rails.rooms.errors.auth.not_running')) }
|
641
649
|
end
|
642
650
|
end
|
643
651
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
it { should respond_with(:success) }
|
651
|
-
it { should render_template(:external) }
|
652
|
-
it { should set_the_flash.to(I18n.t('bigbluebutton_rails.rooms.errors.auth.not_running')) }
|
652
|
+
it "if there's a user logged, should use his name" do
|
653
|
+
user = Factory.build(:user)
|
654
|
+
controller.stub(:bigbluebutton_user).and_return(user)
|
655
|
+
new_room.should_receive(:perform_join).with(user.name, anything, anything). # here's the validation
|
656
|
+
and_return("http://test.com/attendee/join")
|
657
|
+
post :external_auth, :server_id => mocked_server.to_param, :meeting => new_room.meetingid, :user => user_hash
|
653
658
|
end
|
654
659
|
|
655
660
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
Factory.define :bigbluebutton_room do |r|
|
2
|
+
# meetingid with a random factor to avoid duplicated ids in consecutive test runs
|
3
|
+
r.sequence(:meetingid) { |n| "meeting-#{n}-" + SecureRandom.hex(4) }
|
4
|
+
|
2
5
|
r.association :server, :factory => :bigbluebutton_server
|
3
|
-
r.sequence(:meetingid) { |n| "MeetingID#{n}" }
|
4
6
|
r.sequence(:name) { |n| "Name#{n}" }
|
5
7
|
r.attendee_password { Forgery(:basic).password :at_least => 10, :at_most => 16 }
|
6
8
|
r.moderator_password { Forgery(:basic).password :at_least => 10, :at_most => 16 }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BigbluebuttonRoom do
|
4
|
+
|
5
|
+
# to ensure that the migration is correct
|
6
|
+
context "db" do
|
7
|
+
it { should have_db_column(:server_id).of_type(:integer) }
|
8
|
+
it { should have_db_column(:owner_id).of_type(:integer) }
|
9
|
+
it { should have_db_column(:owner_type).of_type(:string) }
|
10
|
+
it { should have_db_column(:meetingid).of_type(:string) }
|
11
|
+
it { should have_db_column(:name).of_type(:string) }
|
12
|
+
it { should have_db_column(:attendee_password).of_type(:string) }
|
13
|
+
it { should have_db_column(:moderator_password).of_type(:string) }
|
14
|
+
it { should have_db_column(:welcome_msg).of_type(:string) }
|
15
|
+
it { should have_db_column(:dial_number).of_type(:string) }
|
16
|
+
it { should have_db_column(:logout_url).of_type(:string) }
|
17
|
+
it { should have_db_column(:voice_bridge).of_type(:string) }
|
18
|
+
it { should have_db_column(:max_participants).of_type(:integer) }
|
19
|
+
it { should have_db_column(:private).of_type(:boolean) }
|
20
|
+
it { should have_db_column(:randomize_meetingid).of_type(:boolean) }
|
21
|
+
it { should have_db_column(:external).of_type(:boolean) }
|
22
|
+
it { should have_db_column(:param).of_type(:string) }
|
23
|
+
it { should have_db_index(:server_id) }
|
24
|
+
it { should have_db_index(:meetingid).unique(true) }
|
25
|
+
it { should have_db_index(:voice_bridge).unique(true) }
|
26
|
+
it "default values" do
|
27
|
+
room = BigbluebuttonRoom.new
|
28
|
+
room.private.should be_false
|
29
|
+
room.randomize_meetingid.should be_true
|
30
|
+
room.external.should be_false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -6,551 +6,546 @@ describe BigbluebuttonRoom do
|
|
6
6
|
BigbluebuttonRoom.new.should be_a_kind_of(ActiveRecord::Base)
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
it
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
9
|
+
before { Factory.create(:bigbluebutton_room) }
|
10
|
+
|
11
|
+
it { should belong_to(:server) }
|
12
|
+
it { should belong_to(:owner) }
|
13
|
+
it { should_not validate_presence_of(:owner_id) }
|
14
|
+
it { should_not validate_presence_of(:owner_type) }
|
15
|
+
|
16
|
+
it { should validate_presence_of(:server_id) }
|
17
|
+
it { should validate_presence_of(:meetingid) }
|
18
|
+
it { should validate_presence_of(:voice_bridge) }
|
19
|
+
it { should validate_presence_of(:name) }
|
20
|
+
it { should validate_presence_of(:param) }
|
21
|
+
|
22
|
+
it { should be_boolean(:private) }
|
23
|
+
it { should be_boolean(:randomize_meetingid) }
|
24
|
+
|
25
|
+
[:name, :server_id, :meetingid, :attendee_password, :moderator_password,
|
26
|
+
:welcome_msg, :owner, :server, :private, :logout_url, :dial_number,
|
27
|
+
:voice_bridge, :max_participants, :owner_id, :owner_type,
|
28
|
+
:randomize_meetingid, :param].
|
29
|
+
each do |attribute|
|
30
|
+
it { should allow_mass_assignment_of(attribute) }
|
31
|
+
end
|
32
|
+
it { should_not allow_mass_assignment_of(:id) }
|
33
|
+
|
34
|
+
it { should validate_uniqueness_of(:meetingid) }
|
35
|
+
it { should validate_uniqueness_of(:name) }
|
36
|
+
it { should validate_uniqueness_of(:voice_bridge) }
|
37
|
+
it { should validate_uniqueness_of(:param) }
|
38
|
+
|
39
|
+
it {
|
40
|
+
room = Factory.create(:bigbluebutton_room)
|
41
|
+
room.server.should_not be_nil
|
42
|
+
}
|
43
|
+
|
44
|
+
it { should ensure_length_of(:meetingid).is_at_least(1).is_at_most(100) }
|
45
|
+
it { should ensure_length_of(:name).is_at_least(1).is_at_most(150) }
|
46
|
+
it { should ensure_length_of(:attendee_password).is_at_most(16) }
|
47
|
+
it { should ensure_length_of(:moderator_password).is_at_most(16) }
|
48
|
+
it { should ensure_length_of(:welcome_msg).is_at_most(250) }
|
49
|
+
it { should ensure_length_of(:param).is_at_least(3) }
|
50
|
+
|
51
|
+
# attr_accessors
|
52
|
+
[:running, :participant_count, :moderator_count, :attendees,
|
53
|
+
:has_been_forcibly_ended, :start_time, :end_time, :external].each do |attr|
|
54
|
+
it { should respond_to(attr) }
|
55
|
+
it { should respond_to("#{attr}=") }
|
36
56
|
end
|
37
57
|
|
38
|
-
context do
|
39
|
-
|
40
|
-
|
41
|
-
it { should belong_to(:server) }
|
42
|
-
it { should belong_to(:owner) }
|
43
|
-
it { should_not validate_presence_of(:owner_id) }
|
44
|
-
it { should_not validate_presence_of(:owner_type) }
|
45
|
-
|
46
|
-
it { should validate_presence_of(:server_id) }
|
47
|
-
it { should validate_presence_of(:meetingid) }
|
48
|
-
it { should validate_presence_of(:voice_bridge) }
|
49
|
-
it { should validate_presence_of(:name) }
|
50
|
-
it { should validate_presence_of(:param) }
|
51
|
-
|
52
|
-
it { should be_boolean(:private) }
|
53
|
-
it { should be_boolean(:randomize_meetingid) }
|
54
|
-
|
55
|
-
[:name, :server_id, :meetingid, :attendee_password, :moderator_password,
|
56
|
-
:welcome_msg, :owner, :server, :private, :logout_url, :dial_number,
|
57
|
-
:voice_bridge, :max_participants, :owner_id, :owner_type,
|
58
|
-
:randomize_meetingid, :param].
|
59
|
-
each do |attribute|
|
60
|
-
it { should allow_mass_assignment_of(attribute) }
|
61
|
-
end
|
62
|
-
it { should_not allow_mass_assignment_of(:id) }
|
63
|
-
|
64
|
-
it { should validate_uniqueness_of(:meetingid) }
|
65
|
-
it { should validate_uniqueness_of(:name) }
|
66
|
-
it { should validate_uniqueness_of(:voice_bridge) }
|
67
|
-
it { should validate_uniqueness_of(:param) }
|
68
|
-
|
58
|
+
context ".to_param" do
|
59
|
+
it { should respond_to(:to_param) }
|
69
60
|
it {
|
70
|
-
|
71
|
-
|
61
|
+
r = Factory.create(:bigbluebutton_room)
|
62
|
+
r.to_param.should be(r.param)
|
72
63
|
}
|
64
|
+
end
|
73
65
|
|
74
|
-
|
75
|
-
it { should ensure_length_of(:name).is_at_least(1).is_at_most(150) }
|
76
|
-
it { should ensure_length_of(:attendee_password).is_at_most(16) }
|
77
|
-
it { should ensure_length_of(:moderator_password).is_at_most(16) }
|
78
|
-
it { should ensure_length_of(:welcome_msg).is_at_most(250) }
|
79
|
-
it { should ensure_length_of(:param).is_at_least(3) }
|
80
|
-
|
81
|
-
# attr_accessors
|
82
|
-
[:running, :participant_count, :moderator_count, :attendees,
|
83
|
-
:has_been_forcibly_ended, :start_time, :end_time, :external].each do |attr|
|
84
|
-
it { should respond_to(attr) }
|
85
|
-
it { should respond_to("#{attr}=") }
|
86
|
-
end
|
87
|
-
|
88
|
-
context ".to_param" do
|
89
|
-
it { should respond_to(:to_param) }
|
90
|
-
it {
|
91
|
-
r = Factory.create(:bigbluebutton_room)
|
92
|
-
r.to_param.should be(r.param)
|
93
|
-
}
|
94
|
-
end
|
66
|
+
it { should respond_to(:is_running?) }
|
95
67
|
|
96
|
-
|
68
|
+
describe "#user_role" do
|
69
|
+
let(:room) { Factory.build(:bigbluebutton_room, :moderator_password => "mod", :attendee_password => "att") }
|
70
|
+
it { should respond_to(:user_role) }
|
71
|
+
it { room.user_role({ :password => room.moderator_password }).should == :moderator }
|
72
|
+
it { room.user_role({ :password => room.attendee_password }).should == :attendee }
|
73
|
+
it { room.user_role({ :password => "wrong" }).should == nil }
|
74
|
+
it { room.user_role({ :password => nil }).should == nil }
|
75
|
+
it { room.user_role({ :not_password => "any" }).should == nil }
|
76
|
+
end
|
97
77
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
78
|
+
describe "#instance_variables_compare" do
|
79
|
+
let(:room) { Factory.create(:bigbluebutton_room) }
|
80
|
+
let(:room2) { BigbluebuttonRoom.last }
|
81
|
+
it { should respond_to(:instance_variables_compare) }
|
82
|
+
it { room.instance_variables_compare(room2).should be_empty }
|
83
|
+
it "compares instance variables" do
|
84
|
+
room2.running = !room.running
|
85
|
+
room.instance_variables_compare(room2).should_not be_empty
|
86
|
+
room.instance_variables_compare(room2).should include(:@running)
|
106
87
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
let(:room2) { BigbluebuttonRoom.last }
|
111
|
-
it { should respond_to(:instance_variables_compare) }
|
112
|
-
it { room.instance_variables_compare(room2).should be_empty }
|
113
|
-
it "compares instance variables" do
|
114
|
-
room2.running = !room.running
|
115
|
-
room.instance_variables_compare(room2).should_not be_empty
|
116
|
-
room.instance_variables_compare(room2).should include(:@running)
|
117
|
-
end
|
118
|
-
it "ignores attributes" do
|
119
|
-
room2.private = !room.private
|
120
|
-
room.instance_variables_compare(room2).should be_empty
|
121
|
-
end
|
88
|
+
it "ignores attributes" do
|
89
|
+
room2.private = !room.private
|
90
|
+
room.instance_variables_compare(room2).should be_empty
|
122
91
|
end
|
92
|
+
end
|
123
93
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
end
|
134
|
-
it "compares attributes" do
|
135
|
-
room2.private = !room.private
|
136
|
-
room.attr_equal?(room2).should be_false
|
137
|
-
end
|
138
|
-
it "compares objects" do
|
139
|
-
room2 = room.clone
|
140
|
-
room.attr_equal?(room2).should be_false
|
141
|
-
end
|
94
|
+
describe "#attr_equal?" do
|
95
|
+
before { Factory.create(:bigbluebutton_room) }
|
96
|
+
let(:room) { BigbluebuttonRoom.last }
|
97
|
+
let(:room2) { BigbluebuttonRoom.last }
|
98
|
+
it { should respond_to(:attr_equal?) }
|
99
|
+
it { room.attr_equal?(room2).should be_true }
|
100
|
+
it "compares instance variables" do
|
101
|
+
room2.running = !room.running
|
102
|
+
room.attr_equal?(room2).should be_false
|
142
103
|
end
|
104
|
+
it "compares attributes" do
|
105
|
+
room2.private = !room.private
|
106
|
+
room.attr_equal?(room2).should be_false
|
107
|
+
end
|
108
|
+
it "compares objects" do
|
109
|
+
room2 = room.clone
|
110
|
+
room.attr_equal?(room2).should be_false
|
111
|
+
end
|
112
|
+
end
|
143
113
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
114
|
+
context "initializes" do
|
115
|
+
let(:room) { BigbluebuttonRoom.new }
|
116
|
+
|
117
|
+
it "fetched attributes before they are fetched" do
|
118
|
+
room.participant_count.should == 0
|
119
|
+
room.moderator_count.should == 0
|
120
|
+
room.running.should be_false
|
121
|
+
room.has_been_forcibly_ended.should be_false
|
122
|
+
room.start_time.should be_nil
|
123
|
+
room.end_time.should be_nil
|
124
|
+
room.attendees.should == []
|
125
|
+
end
|
156
126
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
127
|
+
context "meetingid" do
|
128
|
+
it { room.meetingid.should_not be_nil }
|
129
|
+
it {
|
130
|
+
b = BigbluebuttonRoom.new(:meetingid => "user defined")
|
131
|
+
b.meetingid.should == "user defined"
|
132
|
+
}
|
133
|
+
end
|
164
134
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
end
|
135
|
+
context "voice_bridge" do
|
136
|
+
it {
|
137
|
+
b = BigbluebuttonRoom.new(:voice_bridge => "user defined")
|
138
|
+
b.voice_bridge.should == "user defined"
|
139
|
+
}
|
140
|
+
context "with a random value" do
|
141
|
+
it { room.voice_bridge.should_not be_nil }
|
142
|
+
it { room.voice_bridge.should =~ /7[0-9]{4}/ }
|
143
|
+
it "tries to randomize 10 times if voice_bridge already exists" do
|
144
|
+
room = Factory.create(:bigbluebutton_room, :voice_bridge => "70000")
|
145
|
+
BigbluebuttonRoom.stub!(:find_by_voice_bridge).and_return(room)
|
146
|
+
SecureRandom.should_receive(:random_number).exactly(10).and_return(0000)
|
147
|
+
room2 = BigbluebuttonRoom.new # triggers the random_number calls
|
148
|
+
room2.voice_bridge.should == "70000"
|
180
149
|
end
|
181
150
|
end
|
182
151
|
end
|
152
|
+
end
|
183
153
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
154
|
+
context "param format" do
|
155
|
+
let(:msg) { I18n.t('bigbluebutton_rails.rooms.errors.param_format') }
|
156
|
+
it { should validate_format_of(:param).not_with("123 321").with_message(msg) }
|
157
|
+
it { should validate_format_of(:param).not_with("").with_message(msg) }
|
158
|
+
it { should validate_format_of(:param).not_with("ab@c").with_message(msg) }
|
159
|
+
it { should validate_format_of(:param).not_with("ab#c").with_message(msg) }
|
160
|
+
it { should validate_format_of(:param).not_with("ab$c").with_message(msg) }
|
161
|
+
it { should validate_format_of(:param).not_with("ab%c").with_message(msg) }
|
162
|
+
it { should validate_format_of(:param).not_with("ábcd").with_message(msg) }
|
163
|
+
it { should validate_format_of(:param).not_with("-abc").with_message(msg) }
|
164
|
+
it { should validate_format_of(:param).not_with("abc-").with_message(msg) }
|
165
|
+
it { should validate_format_of(:param).with("_abc").with_message(msg) }
|
166
|
+
it { should validate_format_of(:param).with("abc_").with_message(msg) }
|
167
|
+
it { should validate_format_of(:param).with("abc") }
|
168
|
+
it { should validate_format_of(:param).with("123") }
|
169
|
+
it { should validate_format_of(:param).with("abc-123_d5") }
|
170
|
+
end
|
201
171
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
end
|
211
|
-
it "empty" do
|
212
|
-
@room = Factory.build(:bigbluebutton_room, :param => "",
|
213
|
-
:name => "-My Name@ _Is Odd_-")
|
214
|
-
end
|
172
|
+
context "sets param as the downcased parameterized name if param is" do
|
173
|
+
after :each do
|
174
|
+
@room.save.should be_true
|
175
|
+
@room.param.should == @room.name.downcase.parameterize
|
176
|
+
end
|
177
|
+
it "nil" do
|
178
|
+
@room = Factory.build(:bigbluebutton_room, :param => nil,
|
179
|
+
:name => "-My Name@ _Is Odd_-")
|
215
180
|
end
|
181
|
+
it "empty" do
|
182
|
+
@room = Factory.build(:bigbluebutton_room, :param => "",
|
183
|
+
:name => "-My Name@ _Is Odd_-")
|
184
|
+
end
|
185
|
+
end
|
216
186
|
|
217
|
-
|
218
|
-
|
219
|
-
|
187
|
+
context "using the api" do
|
188
|
+
before { mock_server_and_api }
|
189
|
+
let(:room) { Factory.create(:bigbluebutton_room) }
|
220
190
|
|
221
|
-
|
191
|
+
describe "#fetch_is_running?" do
|
222
192
|
|
223
|
-
|
193
|
+
it { should respond_to(:fetch_is_running?) }
|
224
194
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
it "fetches is_running? when running" do
|
234
|
-
mocked_api.should_receive(:is_meeting_running?).with(room.meetingid).and_return(true)
|
235
|
-
room.server = mocked_server
|
236
|
-
room.fetch_is_running?
|
237
|
-
room.running.should == true
|
238
|
-
room.is_running?.should == true
|
239
|
-
end
|
195
|
+
it "fetches is_running? when not running" do
|
196
|
+
mocked_api.should_receive(:is_meeting_running?).with(room.meetingid).and_return(false)
|
197
|
+
room.server = mocked_server
|
198
|
+
room.fetch_is_running?
|
199
|
+
room.running.should == false
|
200
|
+
room.is_running?.should == false
|
201
|
+
end
|
240
202
|
|
203
|
+
it "fetches is_running? when running" do
|
204
|
+
mocked_api.should_receive(:is_meeting_running?).with(room.meetingid).and_return(true)
|
205
|
+
room.server = mocked_server
|
206
|
+
room.fetch_is_running?
|
207
|
+
room.running.should == true
|
208
|
+
room.is_running?.should == true
|
241
209
|
end
|
242
210
|
|
243
|
-
|
211
|
+
end
|
244
212
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
let(:users) {
|
253
|
-
[
|
254
|
-
{:userID=>"ndw1fnaev0rj", :fullName=>"House M.D.", :role=>:moderator},
|
255
|
-
{:userID=>"gn9e22b7ynna", :fullName=>"Dexter Morgan", :role=>:moderator},
|
256
|
-
{:userID=>"llzihbndryc3", :fullName=>"Cameron Palmer", :role=>:viewer},
|
257
|
-
{:userID=>"rbepbovolsxt", :fullName=>"Trinity", :role=>:viewer}
|
258
|
-
]
|
213
|
+
describe "#fetch_meeting_info" do
|
214
|
+
|
215
|
+
# these hashes should be exactly as returned by bigbluebutton-api-ruby to be sure we are testing it right
|
216
|
+
let(:hash_info) {
|
217
|
+
{ :returncode=>true, :meetingID=>"test_id", :attendeePW=>"1234", :moderatorPW=>"4321",
|
218
|
+
:running=>false, :hasBeenForciblyEnded=>false, :startTime=>nil, :endTime=>nil,
|
219
|
+
:participantCount=>0, :moderatorCount=>0, :attendees=>[], :messageKey=>"", :message=>""
|
259
220
|
}
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
221
|
+
}
|
222
|
+
let(:users) {
|
223
|
+
[
|
224
|
+
{:userID=>"ndw1fnaev0rj", :fullName=>"House M.D.", :role=>:moderator},
|
225
|
+
{:userID=>"gn9e22b7ynna", :fullName=>"Dexter Morgan", :role=>:moderator},
|
226
|
+
{:userID=>"llzihbndryc3", :fullName=>"Cameron Palmer", :role=>:viewer},
|
227
|
+
{:userID=>"rbepbovolsxt", :fullName=>"Trinity", :role=>:viewer}
|
228
|
+
]
|
229
|
+
}
|
230
|
+
let(:hash_info2) {
|
231
|
+
{ :returncode=>true, :meetingID=>"test_id", :attendeePW=>"1234", :moderatorPW=>"4321",
|
232
|
+
:running=>true, :hasBeenForciblyEnded=>false, :startTime=>DateTime.parse("Wed Apr 06 17:09:57 UTC 2011"),
|
233
|
+
:endTime=>nil, :participantCount=>4, :moderatorCount=>2,
|
234
|
+
:attendees=>users, :messageKey=>{ }, :message=>{ }
|
266
235
|
}
|
236
|
+
}
|
267
237
|
|
268
|
-
|
269
|
-
|
270
|
-
it "fetches meeting info when the meeting is not running" do
|
271
|
-
mocked_api.should_receive(:get_meeting_info).
|
272
|
-
with(room.meetingid, room.moderator_password).and_return(hash_info)
|
273
|
-
room.server = mocked_server
|
238
|
+
it { should respond_to(:fetch_meeting_info) }
|
274
239
|
|
275
|
-
|
276
|
-
|
277
|
-
room.
|
278
|
-
|
279
|
-
room.moderator_count.should == 0
|
280
|
-
room.start_time.should == nil
|
281
|
-
room.end_time.should == nil
|
282
|
-
room.attendees.should == []
|
283
|
-
end
|
240
|
+
it "fetches meeting info when the meeting is not running" do
|
241
|
+
mocked_api.should_receive(:get_meeting_info).
|
242
|
+
with(room.meetingid, room.moderator_password).and_return(hash_info)
|
243
|
+
room.server = mocked_server
|
284
244
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
245
|
+
room.fetch_meeting_info
|
246
|
+
room.running.should == false
|
247
|
+
room.has_been_forcibly_ended.should == false
|
248
|
+
room.participant_count.should == 0
|
249
|
+
room.moderator_count.should == 0
|
250
|
+
room.start_time.should == nil
|
251
|
+
room.end_time.should == nil
|
252
|
+
room.attendees.should == []
|
253
|
+
end
|
289
254
|
|
290
|
-
|
291
|
-
|
292
|
-
room.
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
255
|
+
it "fetches meeting info when the meeting is running" do
|
256
|
+
mocked_api.should_receive(:get_meeting_info).
|
257
|
+
with(room.meetingid, room.moderator_password).and_return(hash_info2)
|
258
|
+
room.server = mocked_server
|
259
|
+
|
260
|
+
room.fetch_meeting_info
|
261
|
+
room.running.should == true
|
262
|
+
room.has_been_forcibly_ended.should == false
|
263
|
+
room.participant_count.should == 4
|
264
|
+
room.moderator_count.should == 2
|
265
|
+
room.start_time.should == DateTime.parse("Wed Apr 06 17:09:57 UTC 2011")
|
266
|
+
room.end_time.should == nil
|
267
|
+
|
268
|
+
users.each do |att|
|
269
|
+
attendee = BigbluebuttonAttendee.new
|
270
|
+
attendee.from_hash(att)
|
271
|
+
room.attendees.should include(attendee)
|
303
272
|
end
|
304
|
-
|
305
273
|
end
|
306
274
|
|
307
|
-
|
275
|
+
end
|
308
276
|
|
309
|
-
|
277
|
+
describe "#send_end" do
|
310
278
|
|
311
|
-
|
312
|
-
mocked_api.should_receive(:end_meeting).with(room.meetingid, room.moderator_password)
|
313
|
-
room.server = mocked_server
|
314
|
-
room.send_end
|
315
|
-
end
|
279
|
+
it { should respond_to(:send_end) }
|
316
280
|
|
281
|
+
it "send end_meeting" do
|
282
|
+
mocked_api.should_receive(:end_meeting).with(room.meetingid, room.moderator_password)
|
283
|
+
room.server = mocked_server
|
284
|
+
room.send_end
|
317
285
|
end
|
318
286
|
|
319
|
-
|
320
|
-
let(:attendee_password) { Forgery(:basic).password }
|
321
|
-
let(:moderator_password) { Forgery(:basic).password }
|
322
|
-
let(:hash_create) {
|
323
|
-
{
|
324
|
-
:returncode => "SUCCESS", :meetingID => "test_id",
|
325
|
-
:attendeePW => attendee_password, :moderatorPW => moderator_password,
|
326
|
-
:hasBeenForciblyEnded => "false", :messageKey => {}, :message => {}
|
327
|
-
}
|
328
|
-
}
|
329
|
-
|
330
|
-
it { should respond_to(:send_create) }
|
331
|
-
|
332
|
-
context "send create_meeting" do
|
333
|
-
|
334
|
-
context "for a stored room" do
|
335
|
-
before do
|
336
|
-
mocked_api.should_receive(:create_meeting).
|
337
|
-
with(room.name, room.meetingid, room.moderator_password,
|
338
|
-
room.attendee_password, room.welcome_msg, room.dial_number,
|
339
|
-
room.logout_url, room.max_participants, room.voice_bridge).
|
340
|
-
and_return(hash_create)
|
341
|
-
room.server = mocked_server
|
342
|
-
room.send_create
|
343
|
-
end
|
344
|
-
it { room.attendee_password.should be(attendee_password) }
|
345
|
-
it { room.moderator_password.should be(moderator_password) }
|
346
|
-
it { room.changed?.should be_false }
|
347
|
-
end
|
287
|
+
end
|
348
288
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
end
|
360
|
-
it { new_room.attendee_password.should be(attendee_password) }
|
361
|
-
it { new_room.moderator_password.should be(moderator_password) }
|
362
|
-
it("do not save the record") { new_room.new_record?.should be_true }
|
363
|
-
end
|
289
|
+
describe "#send_create" do
|
290
|
+
let(:attendee_password) { Forgery(:basic).password }
|
291
|
+
let(:moderator_password) { Forgery(:basic).password }
|
292
|
+
let(:hash_create) {
|
293
|
+
{
|
294
|
+
:returncode => "SUCCESS", :meetingID => "test_id",
|
295
|
+
:attendeePW => attendee_password, :moderatorPW => moderator_password,
|
296
|
+
:hasBeenForciblyEnded => "false", :messageKey => {}, :message => {}
|
297
|
+
}
|
298
|
+
}
|
364
299
|
|
365
|
-
|
300
|
+
it { should respond_to(:send_create) }
|
366
301
|
|
367
|
-
|
368
|
-
let(:fail_hash) { { :returncode => true, :meetingID => "new id",
|
369
|
-
:messageKey => "duplicateWarning" } }
|
370
|
-
let(:success_hash) { { :returncode => true, :meetingID => "new id",
|
371
|
-
:messageKey => "" } }
|
372
|
-
let(:new_id) { "new id" }
|
373
|
-
before {
|
374
|
-
room.randomize_meetingid = true
|
375
|
-
room.server = mocked_server
|
376
|
-
}
|
302
|
+
context "send create_meeting" do
|
377
303
|
|
378
|
-
|
379
|
-
|
304
|
+
context "for a stored room" do
|
305
|
+
before do
|
306
|
+
hash = hash_including(:moderatorPW => room.moderator_password, :attendeePW => room.attendee_password,
|
307
|
+
:welcome => room.welcome_msg, :dialNumber => room.dial_number,
|
308
|
+
:logoutURL => room.logout_url, :maxParticipants => room.max_participants,
|
309
|
+
:voiceBridge => room.voice_bridge)
|
380
310
|
mocked_api.should_receive(:create_meeting).
|
381
|
-
with(room.name,
|
382
|
-
|
383
|
-
room.logout_url, room.max_participants, room.voice_bridge)
|
384
|
-
room.send_create
|
385
|
-
end
|
386
|
-
|
387
|
-
it "and tries again on error" do
|
388
|
-
# fails twice and them succeds
|
389
|
-
room.should_receive(:random_meetingid).exactly(3).times.and_return(new_id)
|
390
|
-
mocked_api.should_receive(:create_meeting).
|
391
|
-
with(room.name, new_id, room.moderator_password,
|
392
|
-
room.attendee_password, room.welcome_msg, room.dial_number,
|
393
|
-
room.logout_url, room.max_participants, room.voice_bridge).
|
394
|
-
twice.
|
395
|
-
and_return(fail_hash)
|
396
|
-
mocked_api.should_receive(:create_meeting).
|
397
|
-
with(room.name, new_id, room.moderator_password,
|
398
|
-
room.attendee_password, room.welcome_msg, room.dial_number,
|
399
|
-
room.logout_url, room.max_participants, room.voice_bridge).
|
400
|
-
once.
|
401
|
-
and_return(success_hash)
|
311
|
+
with(room.name, room.meetingid, hash).and_return(hash_create)
|
312
|
+
room.server = mocked_server
|
402
313
|
room.send_create
|
403
314
|
end
|
315
|
+
it { room.attendee_password.should be(attendee_password) }
|
316
|
+
it { room.moderator_password.should be(moderator_password) }
|
317
|
+
it { room.changed?.should be_false }
|
318
|
+
end
|
404
319
|
|
405
|
-
|
406
|
-
|
320
|
+
context "for a new record" do
|
321
|
+
let(:new_room) { Factory.build(:bigbluebutton_room) }
|
322
|
+
before do
|
323
|
+
hash = hash_including(:moderatorPW => new_room.moderator_password, :attendeePW => new_room.attendee_password,
|
324
|
+
:welcome => new_room.welcome_msg, :dialNumber => new_room.dial_number,
|
325
|
+
:logoutURL => new_room.logout_url, :maxParticipants => new_room.max_participants,
|
326
|
+
:voiceBridge => new_room.voice_bridge)
|
407
327
|
mocked_api.should_receive(:create_meeting).
|
408
|
-
with(
|
409
|
-
|
410
|
-
|
411
|
-
exactly(10).times.
|
412
|
-
and_return(fail_hash)
|
413
|
-
room.send_create
|
328
|
+
with(new_room.name, new_room.meetingid, hash).and_return(hash_create)
|
329
|
+
new_room.server = mocked_server
|
330
|
+
new_room.send_create
|
414
331
|
end
|
332
|
+
it { new_room.attendee_password.should be(attendee_password) }
|
333
|
+
it { new_room.moderator_password.should be(moderator_password) }
|
334
|
+
it("and do not save the record") { new_room.new_record?.should be_true }
|
415
335
|
end
|
416
336
|
|
417
337
|
end
|
418
338
|
|
419
|
-
|
420
|
-
let(:
|
339
|
+
context "randomizes meetingid" do
|
340
|
+
let(:fail_hash) { { :returncode => true, :meetingID => "new id",
|
341
|
+
:messageKey => "duplicateWarning" } }
|
342
|
+
let(:success_hash) { { :returncode => true, :meetingID => "new id",
|
343
|
+
:messageKey => "" } }
|
344
|
+
let(:new_id) { "new id" }
|
345
|
+
before {
|
346
|
+
room.randomize_meetingid = true
|
347
|
+
room.server = mocked_server
|
348
|
+
}
|
421
349
|
|
422
|
-
it
|
350
|
+
it "before calling create" do
|
351
|
+
room.should_receive(:random_meetingid).and_return(new_id)
|
352
|
+
hash = hash_including(:moderatorPW => room.moderator_password, :attendeePW => room.attendee_password,
|
353
|
+
:welcome => room.welcome_msg, :dialNumber => room.dial_number,
|
354
|
+
:logoutURL => room.logout_url, :maxParticipants => room.max_participants,
|
355
|
+
:voiceBridge => room.voice_bridge)
|
356
|
+
mocked_api.should_receive(:create_meeting).with(room.name, new_id, hash)
|
357
|
+
room.send_create
|
358
|
+
end
|
423
359
|
|
424
|
-
it "
|
425
|
-
|
426
|
-
|
427
|
-
room.
|
428
|
-
|
360
|
+
it "and tries again on error" do
|
361
|
+
# fails twice and them succeds
|
362
|
+
room.should_receive(:random_meetingid).exactly(3).times.and_return(new_id)
|
363
|
+
hash = hash_including(:moderatorPW => room.moderator_password, :attendeePW => room.attendee_password,
|
364
|
+
:welcome => room.welcome_msg, :dialNumber => room.dial_number,
|
365
|
+
:logoutURL => room.logout_url, :maxParticipants => room.max_participants,
|
366
|
+
:voiceBridge => room.voice_bridge)
|
367
|
+
mocked_api.should_receive(:create_meeting).
|
368
|
+
with(room.name, new_id, hash).twice.and_return(fail_hash)
|
369
|
+
mocked_api.should_receive(:create_meeting).
|
370
|
+
with(room.name, new_id, hash).once.and_return(success_hash)
|
371
|
+
room.send_create
|
429
372
|
end
|
430
373
|
|
431
|
-
it "
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
374
|
+
it "and limits to 10 tries" do
|
375
|
+
room.should_receive(:random_meetingid).exactly(11).times.and_return(new_id)
|
376
|
+
hash = hash_including(:moderatorPW => room.moderator_password, :attendeePW => room.attendee_password,
|
377
|
+
:welcome => room.welcome_msg, :dialNumber => room.dial_number,
|
378
|
+
:logoutURL => room.logout_url, :maxParticipants => room.max_participants,
|
379
|
+
:voiceBridge => room.voice_bridge)
|
380
|
+
mocked_api.should_receive(:create_meeting).
|
381
|
+
with(room.name, new_id, hash).exactly(10).times.and_return(fail_hash)
|
382
|
+
room.send_create
|
436
383
|
end
|
384
|
+
end
|
437
385
|
|
438
|
-
|
439
|
-
|
440
|
-
|
386
|
+
context "uses #full_logout_url when set" do
|
387
|
+
before do
|
388
|
+
room.full_logout_url = "full-version-of-logout-url"
|
389
|
+
hash = hash_including(:moderatorPW => room.moderator_password, :attendeePW => room.attendee_password,
|
390
|
+
:welcome => room.welcome_msg, :dialNumber => room.dial_number,
|
391
|
+
:logoutURL => "full-version-of-logout-url", :maxParticipants => room.max_participants,
|
392
|
+
:voiceBridge => room.voice_bridge)
|
393
|
+
mocked_api.should_receive(:create_meeting).
|
394
|
+
with(room.name, room.meetingid, hash).and_return(hash_create)
|
441
395
|
room.server = mocked_server
|
442
|
-
room.join_url(username, nil, 'pass')
|
443
396
|
end
|
397
|
+
it { room.send_create }
|
444
398
|
end
|
445
399
|
|
446
400
|
end
|
447
401
|
|
448
|
-
|
449
|
-
|
450
|
-
let (:room) { Factory.build(:bigbluebutton_room, :private => true) }
|
451
|
-
it { room.should_not allow_value('').for(:moderator_password) }
|
452
|
-
it { room.should_not allow_value('').for(:attendee_password) }
|
453
|
-
end
|
402
|
+
describe "#join_url" do
|
403
|
+
let(:username) { Forgery(:name).full_name }
|
454
404
|
|
455
|
-
|
456
|
-
let (:room) { Factory.build(:bigbluebutton_room, :private => false) }
|
457
|
-
it { room.should allow_value('').for(:moderator_password) }
|
458
|
-
it { room.should allow_value('').for(:attendee_password) }
|
459
|
-
end
|
460
|
-
end
|
405
|
+
it { should respond_to(:join_url) }
|
461
406
|
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
407
|
+
it "with moderator role" do
|
408
|
+
mocked_api.should_receive(:join_meeting_url).
|
409
|
+
with(room.meetingid, username, room.moderator_password)
|
410
|
+
room.server = mocked_server
|
411
|
+
room.join_url(username, :moderator)
|
467
412
|
end
|
468
413
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
414
|
+
it "with attendee role" do
|
415
|
+
mocked_api.should_receive(:join_meeting_url).
|
416
|
+
with(room.meetingid, username, room.attendee_password)
|
417
|
+
room.server = mocked_server
|
418
|
+
room.join_url(username, :attendee)
|
473
419
|
end
|
474
420
|
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
421
|
+
it "without a role" do
|
422
|
+
mocked_api.should_receive(:join_meeting_url).
|
423
|
+
with(room.meetingid, username, 'pass')
|
424
|
+
room.server = mocked_server
|
425
|
+
room.join_url(username, nil, 'pass')
|
479
426
|
end
|
427
|
+
end
|
480
428
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
429
|
+
end
|
430
|
+
|
431
|
+
context "validates passwords" do
|
432
|
+
context "for private rooms" do
|
433
|
+
let (:room) { Factory.build(:bigbluebutton_room, :private => true) }
|
434
|
+
it { room.should_not allow_value('').for(:moderator_password) }
|
435
|
+
it { room.should_not allow_value('').for(:attendee_password) }
|
486
436
|
end
|
487
437
|
|
488
|
-
|
489
|
-
let(:room) { Factory.
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
context "when the conference is running" do
|
496
|
-
before {
|
497
|
-
room.should_receive(:is_running?).and_return(true)
|
498
|
-
room.should_receive(:join_url).with(user.name, :attendee).
|
499
|
-
and_return("http://test.com/attendee/join")
|
500
|
-
}
|
501
|
-
subject { room.perform_join(user.name, :attendee) }
|
502
|
-
it { should == "http://test.com/attendee/join" }
|
503
|
-
end
|
438
|
+
context "for public rooms" do
|
439
|
+
let (:room) { Factory.build(:bigbluebutton_room, :private => false) }
|
440
|
+
it { room.should allow_value('').for(:moderator_password) }
|
441
|
+
it { room.should allow_value('').for(:attendee_password) }
|
442
|
+
end
|
443
|
+
end
|
504
444
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
445
|
+
describe "#add_domain_to_logout_url" do
|
446
|
+
context "when logout_url has a path only" do
|
447
|
+
let(:room) { Factory.create(:bigbluebutton_room, :logout_url => '/only/path') }
|
448
|
+
before(:each) { room.add_domain_to_logout_url("HTTP://", "test.com:80") }
|
449
|
+
it { room.full_logout_url.should == "http://test.com:80/only/path" }
|
450
|
+
it { room.logout_url.should == "/only/path" }
|
451
|
+
it { BigbluebuttonRoom.find(room.id).logout_url.should == "/only/path" }
|
452
|
+
end
|
453
|
+
|
454
|
+
context "when logout_url has a path and domain" do
|
455
|
+
let(:room) { Factory.create(:bigbluebutton_room, :logout_url => 'other.com/only/path') }
|
456
|
+
before(:each) { room.add_domain_to_logout_url("HTTP://", "test.com:80") }
|
457
|
+
it { room.full_logout_url.should == "http://other.com/only/path" }
|
458
|
+
it { room.logout_url.should == "other.com/only/path" }
|
459
|
+
it { BigbluebuttonRoom.find(room.id).logout_url.should == "other.com/only/path" }
|
460
|
+
end
|
461
|
+
|
462
|
+
context "when logout_url has a path, domain and protocol" do
|
463
|
+
let(:room) { Factory.create(:bigbluebutton_room, :logout_url => 'HTTPS://other.com/only/path') }
|
464
|
+
before(:each) { room.add_domain_to_logout_url("HTTP://", "test.com:80") }
|
465
|
+
it { room.full_logout_url.should == "https://other.com/only/path" }
|
466
|
+
it { room.logout_url.should == "HTTPS://other.com/only/path" }
|
467
|
+
it { BigbluebuttonRoom.find(room.id).logout_url.should == "HTTPS://other.com/only/path" }
|
468
|
+
end
|
469
|
+
|
470
|
+
context "does nothing if logout_url is nil" do
|
471
|
+
let(:room) { Factory.create(:bigbluebutton_room, :logout_url => nil) }
|
472
|
+
before(:each) { room.add_domain_to_logout_url("HTTP://", "test.com:80") }
|
473
|
+
it { room.full_logout_url.should be_nil }
|
474
|
+
it { room.logout_url.should be_nil }
|
475
|
+
it { BigbluebuttonRoom.find(room.id).logout_url.should be_nil }
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
describe "#perform_join" do
|
480
|
+
let(:room) { Factory.create(:bigbluebutton_room) }
|
481
|
+
let(:user) { Factory.build(:user) }
|
482
|
+
|
483
|
+
context "for an attendee" do
|
484
|
+
before { room.should_receive(:fetch_is_running?) }
|
485
|
+
|
486
|
+
context "when the conference is running" do
|
487
|
+
before {
|
488
|
+
room.should_receive(:is_running?).and_return(true)
|
489
|
+
room.should_receive(:join_url).with(user.name, :attendee).
|
490
|
+
and_return("http://test.com/attendee/join")
|
491
|
+
}
|
492
|
+
subject { room.perform_join(user.name, :attendee) }
|
493
|
+
it { should == "http://test.com/attendee/join" }
|
510
494
|
end
|
511
495
|
|
512
|
-
context "
|
513
|
-
before { room.should_receive(:
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
room.should_receive(:join_url).with(user.name, :moderator).
|
519
|
-
and_return("http://test.com/moderator/join")
|
520
|
-
}
|
521
|
-
subject { room.perform_join(user.name, :moderator) }
|
522
|
-
it { should == "http://test.com/moderator/join" }
|
523
|
-
end
|
496
|
+
context "when the conference is not running" do
|
497
|
+
before { room.should_receive(:is_running?).and_return(false) }
|
498
|
+
subject { room.perform_join(user.name, :attendee) }
|
499
|
+
it { should be_nil }
|
500
|
+
end
|
501
|
+
end
|
524
502
|
|
525
|
-
|
526
|
-
|
527
|
-
room.should_receive(:is_running?).and_return(false)
|
528
|
-
room.should_receive(:send_create)
|
529
|
-
room.should_receive(:join_url).with(user.name, :moderator).
|
530
|
-
and_return("http://test.com/moderator/join")
|
531
|
-
}
|
532
|
-
subject { room.perform_join(user.name, :moderator) }
|
533
|
-
it { should == "http://test.com/moderator/join" }
|
534
|
-
end
|
503
|
+
context "for a moderator" do
|
504
|
+
before { room.should_receive(:fetch_is_running?) }
|
535
505
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
506
|
+
context "when the conference is running" do
|
507
|
+
before {
|
508
|
+
room.should_receive(:is_running?).and_return(true)
|
509
|
+
room.should_receive(:join_url).with(user.name, :moderator).
|
510
|
+
and_return("http://test.com/moderator/join")
|
511
|
+
}
|
512
|
+
subject { room.perform_join(user.name, :moderator) }
|
513
|
+
it { should == "http://test.com/moderator/join" }
|
514
|
+
end
|
515
|
+
|
516
|
+
context "when the conference is not running" do
|
517
|
+
before {
|
518
|
+
room.should_receive(:is_running?).and_return(false)
|
519
|
+
room.should_receive(:send_create)
|
520
|
+
room.should_receive(:join_url).with(user.name, :moderator).
|
521
|
+
and_return("http://test.com/moderator/join")
|
522
|
+
}
|
523
|
+
subject { room.perform_join(user.name, :moderator) }
|
524
|
+
it { should == "http://test.com/moderator/join" }
|
525
|
+
end
|
549
526
|
|
527
|
+
context "when the arg 'request' is informed" do
|
528
|
+
let(:request) { stub(ActionController::Request) }
|
529
|
+
before {
|
530
|
+
request.stub!(:protocol).and_return("HTTP://")
|
531
|
+
request.stub!(:host_with_port).and_return("test.com:80")
|
532
|
+
room.should_receive(:add_domain_to_logout_url).with("HTTP://", "test.com:80")
|
533
|
+
room.should_receive(:is_running?).and_return(true)
|
534
|
+
room.should_receive(:join_url).with(user.name, :moderator).
|
535
|
+
and_return("http://test.com/moderator/join")
|
536
|
+
}
|
537
|
+
subject { room.perform_join(user.name, :moderator, request) }
|
538
|
+
it { should == "http://test.com/moderator/join" }
|
550
539
|
end
|
551
540
|
|
552
541
|
end
|
553
542
|
|
554
543
|
end
|
555
544
|
|
545
|
+
describe "#full_logout_url" do
|
546
|
+
subject { BigbluebuttonRoom.new }
|
547
|
+
it { should respond_to(:full_logout_url) }
|
548
|
+
it { should respond_to(:"full_logout_url=") }
|
549
|
+
end
|
550
|
+
|
556
551
|
end
|