canvas_connect 0.0.1
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 +19 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +661 -0
- data/README.md +28 -0
- data/Rakefile +1 -0
- data/app/models/adobe_connect_conference.rb +190 -0
- data/app/views/plugins/_connect_settings.html.erb +37 -0
- data/canvas_connect.gemspec +20 -0
- data/lib/canvas/plugins/adobe_connect.rb +44 -0
- data/lib/canvas/plugins/validators/adobe_connect_validator.rb +78 -0
- data/lib/canvas_connect.rb +67 -0
- data/lib/canvas_connect/connect_user.rb +105 -0
- data/lib/canvas_connect/meeting_folder.rb +60 -0
- data/lib/canvas_connect/response.rb +29 -0
- data/lib/canvas_connect/service.rb +138 -0
- data/lib/canvas_connect/version.rb +22 -0
- data/spec_canvas/lib/canvas/plugins/validators/adobe_connect_validator_spec.rb +45 -0
- data/spec_canvas/lib/canvas_connect/response_spec.rb +52 -0
- data/spec_canvas/lib/canvas_connect/service_spec.rb +104 -0
- data/spec_canvas/models/adobe_connect_conference_spec.rb +48 -0
- metadata +69 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
module CanvasConnect
|
|
20
|
+
class ConnectUser
|
|
21
|
+
attr_accessor :id, :canvas_user
|
|
22
|
+
attr_reader :client
|
|
23
|
+
|
|
24
|
+
# Public: Create a new ConnectUser instance.
|
|
25
|
+
#
|
|
26
|
+
# canvas_user - A Canvas User object.
|
|
27
|
+
# client - A CanvasConnect::Service instance. (default: CanvasConnect.client)
|
|
28
|
+
def initialize(canvas_user, client = CanvasConnect.client)
|
|
29
|
+
@canvas_user, @client = [canvas_user, client]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Public: Save this user to the Adobe Connect instance.
|
|
33
|
+
#
|
|
34
|
+
# Returns true.
|
|
35
|
+
def save
|
|
36
|
+
response = @client.principal_update(
|
|
37
|
+
:first_name => @canvas_user.first_name.present? ? @canvas_user.first_name : 'Unknown',
|
|
38
|
+
:last_name => @canvas_user.last_name.present? ? @canvas_user.last_name : 'Unknown',
|
|
39
|
+
:login => username,
|
|
40
|
+
:password => password,
|
|
41
|
+
:type => 'user',
|
|
42
|
+
:has_children => 0,
|
|
43
|
+
:email => @canvas_user.email)
|
|
44
|
+
@id = response.at_xpath('//principal')['principal-id']
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Public: Generate a unique Adobe Connect username for this user.
|
|
49
|
+
#
|
|
50
|
+
# Examples
|
|
51
|
+
#
|
|
52
|
+
# connect_user.username #=> canvas_user_15
|
|
53
|
+
#
|
|
54
|
+
# Returns a username string.
|
|
55
|
+
def username
|
|
56
|
+
"canvas_user_#{@canvas_user.id}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Internal: Generate a 10 character password for Adobe Connect.
|
|
60
|
+
#
|
|
61
|
+
# Returns a password string.
|
|
62
|
+
def password
|
|
63
|
+
@password ||= Digest::SHA1.hexdigest(@canvas_user.uuid)[0..9]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class << self
|
|
67
|
+
# Public: Find a Canvas user on an Adobe Connect instance.
|
|
68
|
+
#
|
|
69
|
+
# user - A Canvas user object.
|
|
70
|
+
#
|
|
71
|
+
# Returns a CanvasConnect::ConnectUser or nil.
|
|
72
|
+
def find(user)
|
|
73
|
+
connect_user = ConnectUser.new(user)
|
|
74
|
+
response = connect_user.client.principal_list(:filter_login => connect_user.username)
|
|
75
|
+
if found_user = response.at_xpath('//principal')
|
|
76
|
+
connect_user.id = found_user['principal-id']
|
|
77
|
+
connect_user
|
|
78
|
+
else
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Public: Create an Adobe Connect user for the given Canvas user.
|
|
84
|
+
#
|
|
85
|
+
# user - The Canvas user to create in Connect.
|
|
86
|
+
#
|
|
87
|
+
# Returns a new CanvasConnect::ConnectUser.
|
|
88
|
+
def create(user)
|
|
89
|
+
new_user = ConnectUser.new(user)
|
|
90
|
+
new_user.save
|
|
91
|
+
|
|
92
|
+
new_user
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Public: Find the given user in Connect or, if they don't exist, create them.
|
|
96
|
+
#
|
|
97
|
+
# user - A Canvas user.
|
|
98
|
+
#
|
|
99
|
+
# Returns a CanvasConnect::ConnectUser.
|
|
100
|
+
def find_or_create(user)
|
|
101
|
+
find(user) || create(user)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
module CanvasConnect
|
|
20
|
+
class MeetingFolder
|
|
21
|
+
attr_accessor :name
|
|
22
|
+
|
|
23
|
+
extend ActiveSupport::Memoizable
|
|
24
|
+
|
|
25
|
+
# Public: Create a new MeetingFolder.
|
|
26
|
+
#
|
|
27
|
+
# name - The name of the folder on Adobe Connect (must already exist).
|
|
28
|
+
# client - A CanvasConnect::Service to make requests with. (default: CanvasConnect.client)
|
|
29
|
+
def initialize(name, client = CanvasConnect.client)
|
|
30
|
+
@name = name
|
|
31
|
+
@client = client
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Public: Get the SCO ID for this folder.
|
|
35
|
+
#
|
|
36
|
+
# Returns an SCO ID string or nil if it doesn't exist.
|
|
37
|
+
def id
|
|
38
|
+
container = @client.sco_shortcuts.at_xpath('//sco[@type="user-meetings"]')
|
|
39
|
+
remote_folder = @client.sco_expanded_contents(:sco_id => container['sco-id'],
|
|
40
|
+
:filter_name => @name)
|
|
41
|
+
|
|
42
|
+
remote_folder.at_xpath('//sco')['sco-id']
|
|
43
|
+
rescue NoMethodError
|
|
44
|
+
# Return nil if the container or remote_folder can't be found.
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
memoize :id
|
|
48
|
+
|
|
49
|
+
# Public: Get the URL path for this folder.
|
|
50
|
+
#
|
|
51
|
+
# Returns a URL fragment string or nil if it can't be queried.
|
|
52
|
+
def url_path
|
|
53
|
+
response = @client.sco_info(:sco_id => @id)
|
|
54
|
+
response.at_xpath('//url-path').text
|
|
55
|
+
rescue NoMethodError
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
memoize :url_path
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
module CanvasConnect
|
|
20
|
+
class Response < SimpleDelegator
|
|
21
|
+
attr_reader :status, :headers, :body
|
|
22
|
+
|
|
23
|
+
def initialize(status, headers, body)
|
|
24
|
+
@status, @headers, @body = [status.to_i, headers, Nokogiri::XML(body)]
|
|
25
|
+
super(@body)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
module CanvasConnect
|
|
20
|
+
class Service
|
|
21
|
+
attr_reader :username, :domain, :is_authenticated
|
|
22
|
+
|
|
23
|
+
def initialize(username, password, domain)
|
|
24
|
+
@username, @password, @domain = [username, password, domain]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Public: Authenticate against the Adobe Connect server.
|
|
28
|
+
#
|
|
29
|
+
# Returns true.
|
|
30
|
+
def log_in
|
|
31
|
+
unless logged_in?
|
|
32
|
+
response = login(:login => @username, :password => @password)
|
|
33
|
+
if response.xpath('//status[@code="ok"]').empty?
|
|
34
|
+
raise ConnectionError.new("Could not log in to #{@domain}.")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@is_authenticated = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Public: Determine if the current session is authenticated.
|
|
44
|
+
#
|
|
45
|
+
# Returns a boolean.
|
|
46
|
+
def logged_in?
|
|
47
|
+
@is_authenticated
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Public: Proxy any unknown methods to the Adobe Connect API.
|
|
51
|
+
#
|
|
52
|
+
# method - The snake-cased name of an Adobe Connect method, e.g. `common_info`.
|
|
53
|
+
# args - Two optional arguments: a hash of GET params, and a skip_session boolean.
|
|
54
|
+
#
|
|
55
|
+
# Returns a CanvasConnect::Response.
|
|
56
|
+
def method_missing(method, *args)
|
|
57
|
+
action = "#{method}".dasherize
|
|
58
|
+
params, skip_session = args
|
|
59
|
+
params ||= {}
|
|
60
|
+
|
|
61
|
+
request(action, params, !skip_session)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Public: Create a new Connect session for the given user.
|
|
65
|
+
#
|
|
66
|
+
# user - A CanvasConnect::ConnectUser.
|
|
67
|
+
# domain - The domain to authenticate against.
|
|
68
|
+
def self.user_session(user, domain)
|
|
69
|
+
service = CanvasConnect::Service.new(user.username, user.password, domain)
|
|
70
|
+
service.log_in
|
|
71
|
+
|
|
72
|
+
service.session_key
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Public: Get a session token for future requests.
|
|
76
|
+
#
|
|
77
|
+
# Returns a session token.
|
|
78
|
+
def session_key
|
|
79
|
+
unless @session_key
|
|
80
|
+
response = request('common-info', {}, false)
|
|
81
|
+
@session_key = response.xpath('//cookie').text
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@session_key
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
protected
|
|
88
|
+
# Internal: Create and/or return a Net::HTTP instance.
|
|
89
|
+
#
|
|
90
|
+
# Returns a Net::HTTP instance.
|
|
91
|
+
def client
|
|
92
|
+
unless @client
|
|
93
|
+
uri = URI.parse(@domain)
|
|
94
|
+
@client = Net::HTTP.new(uri.host, uri.port)
|
|
95
|
+
@client.use_ssl = (uri.scheme == 'https')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
@client
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Internal: Make a request to the Adobe Connect API.
|
|
102
|
+
#
|
|
103
|
+
# action - The name of the Connect API action to call.
|
|
104
|
+
# params - A hash of parameters to pass with the request. (default: {})
|
|
105
|
+
# with_session - If true, make the request inside a new or existing session. (default: true)
|
|
106
|
+
#
|
|
107
|
+
# Returns a CanvasConnect::Response object.
|
|
108
|
+
def request(action, params = {}, with_session = true)
|
|
109
|
+
params[:session] = session_key if with_session
|
|
110
|
+
response = client.get("/api/xml?action=#{action}#{format_params(params)}")
|
|
111
|
+
|
|
112
|
+
CanvasConnect::Response.new(response.code, response.each_header { |h| }, response.body)
|
|
113
|
+
rescue SocketError, TimeoutError => e
|
|
114
|
+
# Return an empty, timed-out request.
|
|
115
|
+
Rails.logger.error "Adobe Connect Request Error on #{action}: #{e.message}"
|
|
116
|
+
CanvasConnect::Response.new(408, {}, '')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Internal: Convert snake-cased hash keys to dashed.
|
|
120
|
+
#
|
|
121
|
+
# params - A hash of parameters with snake-cased string or symbol keys.
|
|
122
|
+
#
|
|
123
|
+
# Examples
|
|
124
|
+
#
|
|
125
|
+
# format_params({param_name: 'value', other_param: 'value 2'})
|
|
126
|
+
#
|
|
127
|
+
# # Returns "¶m-name=value&other-param=value%202"
|
|
128
|
+
#
|
|
129
|
+
# Returns a query string prefixed with a '&' (because it assumes action will be included).
|
|
130
|
+
def format_params(params)
|
|
131
|
+
params.inject(['']) do |arr, p|
|
|
132
|
+
key, value = p
|
|
133
|
+
arr << "#{key.to_s.dasherize}=#{URI.escape(value.to_s)}"
|
|
134
|
+
end.join('&')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
module CanvasConnect
|
|
20
|
+
VERSION = "0.0.1"
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../../../../../../spec/spec_helper')
|
|
20
|
+
|
|
21
|
+
describe Canvas::Plugins::Validators::AdobeConnectValidator do
|
|
22
|
+
let(:plugin_setting) { mock }
|
|
23
|
+
|
|
24
|
+
subject { Canvas::Plugins::Validators::AdobeConnectValidator }
|
|
25
|
+
|
|
26
|
+
it 'should allow an empty hash' do
|
|
27
|
+
subject.validate({}, plugin_setting).should eql Hash.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'should error on missing keys' do
|
|
31
|
+
plugin_setting.expects(:errors).returns(stub(:add_to_base => true))
|
|
32
|
+
subject.validate({:domain => 'example.com'}, plugin_setting).should be_false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'should pass if all keys exist' do
|
|
36
|
+
valid_keys = {
|
|
37
|
+
:domain => 'example.com',
|
|
38
|
+
:login => 'username',
|
|
39
|
+
:password => 'password',
|
|
40
|
+
:meeting_container => 'folder_name'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
subject.validate(valid_keys, plugin_setting).should eql valid_keys
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Canvas.
|
|
5
|
+
#
|
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
# Software Foundation, version 3 of the License.
|
|
9
|
+
#
|
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
# details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../../../../spec/spec_helper')
|
|
20
|
+
|
|
21
|
+
describe CanvasConnect::Response do
|
|
22
|
+
let(:body) { '<?xml version="1.0"?><items><item>Item Name</item></items>' }
|
|
23
|
+
subject { CanvasConnect::Response.new(200, {}, body) }
|
|
24
|
+
|
|
25
|
+
it { should respond_to(:status) }
|
|
26
|
+
it { should respond_to(:headers) }
|
|
27
|
+
it { should respond_to(:body) }
|
|
28
|
+
|
|
29
|
+
its(:body) { should be_an_instance_of(Nokogiri::XML::Document) }
|
|
30
|
+
|
|
31
|
+
describe 'initialize' do
|
|
32
|
+
it 'should require a status' do
|
|
33
|
+
lambda { CanvasConnect::Response.new }.should raise_error(ArgumentError)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'should require headers' do
|
|
37
|
+
lambda { CanvasConnect::Response.new(200) }.should raise_error(ArgumentError)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'should require a body' do
|
|
41
|
+
lambda { CanvasConnect::Response.new(200, {}) }.should raise_error(ArgumentError)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe 'simple delegator' do
|
|
46
|
+
let(:body) { '<?xml version="1.0"?><items><item>Item Name</item></items>' }
|
|
47
|
+
|
|
48
|
+
it 'should delegate to body' do
|
|
49
|
+
subject.xpath('//item').text.should eql 'Item Name'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|