forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_spec_helper.rb
More file actions
269 lines (241 loc) · 10.5 KB
/
Copy pathapi_spec_helper.rb
File metadata and controls
269 lines (241 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require 'nokogiri'
RSpec::configure do |c|
# rspec-rails 3 will no longer automatically infer an example group's spec type
# from the file location. You can explicitly opt-in to the feature using this
# config option.
# To explicitly tag specs without using automatic inference, set the `:type`
# metadata manually:
#
# describe ThingsController, :type => :controller do
# # Equivalent to being in spec/controllers
# end
c.infer_spec_type_from_file_location!
end
class HashWithDupCheck < Hash
def []=(k,v)
if self.key?(k)
raise ArgumentError, "key already exists: #{k.inspect}"
else
super
end
end
end
# make an API call using the given method (GET/PUT/POST/DELETE),
# to the given path (e.g. /api/v1/courses). params will be verified to match the
# params generated by the Rails routing engine. body_params are params in a
# PUT/POST that are included in the body rather than the URI, and therefore
# don't affect routing.
def api_call(method, path, params, body_params = {}, headers = {}, opts = {})
raw_api_call(method, path, params, body_params, headers, opts)
if opts[:expected_status]
assert_status(opts[:expected_status])
end
if response.headers['Link']
# make sure that the link header is properly formed
Api.parse_pagination_links(response.headers['Link'])
end
case params[:format]
when 'json'
raise "got non-json" unless response.header[content_type_key] == 'application/json; charset=utf-8'
body = response.body
if body.respond_to?(:call)
StringIO.new.tap { |sio| body.call(nil, sio); body = sio.string }
end
body.sub!(%r{^while\(1\);}, '')
# Check that the body doesn't have any duplicate keys. this can happen if
# you add both a string and a symbol to the hash before calling to_json on
# it.
# The ruby JSON gem allows this, and it's technically valid JSON to have
# duplicate names in an object ("names SHOULD be unique"), but it's silly
# and we're not gonna let it slip through again.
JSON.parse(body, :object_class => HashWithDupCheck)
else
raise("Don't know how to handle response format #{params[:format]}")
end
end
def jsonapi_call?(headers)
headers['Accept'] == 'application/vnd.api+json'
end
# like api_call, but performed by the specified user instead of @user
def api_call_as_user(user, method, path, params, body_params = {}, headers = {}, opts = {})
token = access_token_for_user(user)
headers['Authorization'] = "Bearer #{token}"
account = opts[:domain_root_account] || Account.default
user.pseudonyms.reload
account.pseudonyms.create!(:unique_id => "#{user.id}@example.com", :user => user) unless SisPseudonym.for(user, account, type: :implicit, require_sis: false)
Pseudonym.any_instance.stubs(:works_for_account?).returns(true)
api_call(method, path, params, body_params, headers, opts)
end
$spec_api_tokens = {}
def access_token_for_user(user)
token = $spec_api_tokens[user]
unless token
token = $spec_api_tokens[user] = user.access_tokens.create!(:purpose => "test").full_token
end
token
end
# like api_call, but don't assume success and a json response.
def raw_api_call(method, path, params, body_params = {}, headers = {}, opts = {})
path = path.sub(%r{\Ahttps?://[^/]+}, '') # remove protocol+host
enable_forgery_protection do
route_params = params_from_with_nesting(method, path)
route_params.each do |key, value|
raise "Expected value of params[\'#{key}\'] to equal #{value}, actual: #{params[key]}" unless params[key].to_s == value.to_s
end
if @use_basic_auth
user_session(@user)
else
headers['HTTP_AUTHORIZATION'] = headers['Authorization'] if headers.key?('Authorization')
if !params.key?(:api_key) && !params.key?(:access_token) && !headers.key?('HTTP_AUTHORIZATION') && @user
token = access_token_for_user(@user)
headers['HTTP_AUTHORIZATION'] = "Bearer #{token}"
account = opts[:domain_root_account] || Account.default
Pseudonym.any_instance.stubs(:works_for_account?).returns(true)
account.pseudonyms.create!(:unique_id => "#{@user.id}@example.com", :user => @user) unless @user.all_active_pseudonyms(:reload) && SisPseudonym.for(@user, account, type: :implicit, require_sis: false)
end
end
LoadAccount.stubs(:default_domain_root_account).returns(opts[:domain_root_account]) if opts.has_key?(:domain_root_account)
__send__(method, path, params.reject { |k,v| route_params.keys.include?(k.to_sym) }.merge(body_params), headers)
end
end
def follow_pagination_link(rel, params={})
links = Api.parse_pagination_links(response.headers['Link'])
link = links.find{ |l| l[:rel] == rel }
link.delete(:rel)
uri = link.delete(:uri).to_s
link.each{ |key,value| params[key.to_sym] = value }
api_call(:get, uri, params)
end
def params_from_with_nesting(method, path)
path, querystring = path.split('?')
params = CanvasRails::Application.routes.recognize_path(path, :method => method)
querystring.blank? ? params : params.merge(Rack::Utils.parse_nested_query(querystring).symbolize_keys!)
end
def api_json_response(objects, opts = nil)
JSON.parse(objects.to_json(opts.merge(:include_root => false)))
end
def check_document(html, course, attachment, include_verifiers)
doc = Nokogiri::HTML::DocumentFragment.parse(html)
img1 = doc.at_css('img#1')
expect(img1).to be_present
params = include_verifiers ? "?verifier=#{attachment.uuid}" : ""
expect(img1['src']).to eq "http://www.example.com/courses/#{course.id}/files/#{attachment.id}/preview#{params}"
img2 = doc.at_css('img#2')
expect(img2).to be_present
expect(img2['src']).to eq "http://www.example.com/courses/#{course.id}/files/#{attachment.id}/download#{params}"
video = doc.at_css('video')
expect(video).to be_present
expect(video['poster']).to match(%r{http://www.example.com/media_objects/qwerty/thumbnail})
expect(video['src']).to match(%r{http://www.example.com/courses/#{course.id}/media_download})
expect(video['src']).to match(%r{entryId=qwerty})
expect(doc.css('a').last['data-api-endpoint']).to match(%r{http://www.example.com/api/v1/courses/#{course.id}/pages/awesome-page})
expect(doc.css('a').last['data-api-returntype']).to eq 'Page'
end
# passes the cb a piece of user content html text. the block should return the
# response from the api for that field, which will be verified for correctness.
def should_translate_user_content(course, include_verifiers=true)
attachment = attachment_model(:context => course)
content = %{
<p>
Hello, students.<br>
This will explain everything: <img id="1" src="/courses/#{course.id}/files/#{attachment.id}/preview" alt="important">
This won't explain anything: <img id="2" src="/courses/#{course.id}/files/#{attachment.id}/download" alt="important">
Also, watch this awesome video: <a href="/media_objects/qwerty" class="instructure_inline_media_comment video_comment" id="media_comment_qwerty"><img></a>
And refer to this <a href="/courses/#{course.id}/pages/awesome-page">awesome wiki page</a>.
</p>
}
html = yield content
check_document(html, course, attachment, include_verifiers)
if include_verifiers
# try again but with cookie auth; shouldn't have verifiers now
@use_basic_auth = true
html = yield content
check_document(html, course, attachment, false)
end
end
def should_process_incoming_user_content(context)
attachment_model(:context => context)
incoming_content = "<p>content blahblahblah <a href=\"/files/#{@attachment.id}/download?a=1&verifier=2&b=3\">haha</a></p>"
saved_content = yield incoming_content
expect(saved_content).to eq "<p>content blahblahblah <a href=\"/#{context.class.to_s.underscore.pluralize}/#{context.id}/files/#{@attachment.id}/download?a=1&b=3\">haha</a></p>"
end
def verify_json_error(error, field, code, message = nil)
expect(error["field"]).to eq field
expect(error["code"]).to eq code
expect(error["message"]).to eq message if message
end
# Assert the provided JSON hash complies with the JSON-API format specification.
#
# The following tests will be carried out:
#
# - all resource entries must be wrapped inside arrays, even if the set
# includes only a single resource entry
# - when associations are present, a "meta" entry should be present and
# it should indicate the primary set in the "primaryCollection" key
#
# @param [Hash] json
# The JSON construct to test.
#
# @param [String] primary_set
# Name of the primary resource the construct represents, i.e, the model
# the API endpoint represents, like 'quiz', 'assignment', or 'submission'.
#
# @param [Array<String>] associations
# An optional set of associated resources that should be included with
# the primary resource (e.g, a user, an assignment, a submission, etc.).
#
# @example Testing a Quiz API model:
# test_jsonapi_compliance!(json, 'quiz')
#
# @example Testing a Quiz API model with its assignment included:
# test_jsonapi_compliance!(json, 'quiz', [ 'assignment' ])
#
# @example A complying construct of a Quiz Submission with its Assignment:
#
# {
# "quiz_submissions": [{
# "id": 10,
# "assignment_id": 5
# }],
# "assignments": [{
# "id": 5
# }],
# "meta": {
# "primaryCollection": "quiz_submissions"
# }
# }
#
def assert_jsonapi_compliance(json, primary_set, associations = [])
required_keys = [ primary_set ]
if associations.any?
required_keys.concat associations.map { |s| s.pluralize }
required_keys << 'meta'
end
# test key values instead of nr. of keys so we get meaningful failures
expect(json.keys.sort).to eq required_keys.sort
required_keys.each do |key|
expect(json).to be_has_key(key)
expect(json[key].is_a?(Array)).to be_truthy unless key == 'meta'
end
if associations.any?
expect(json['meta']['primaryCollection']).to eq primary_set
end
end