api: maintain query parameters in pagination links
fixes #10491 test plan: - make an api call to a paginated endpoint that has a query parameter as part of the call (courses/<id>/users with enrollment_type=student is a good one) - the pagination link header links that come back should maintain the query parameter (in the example above, they would include enrollment_type=student) - also try one that has an "include[]=" type parameter - read the api pagination documentation (linked from the api sidebar) and make sure it makes sense. Change-Id: I6c1649513553bb2ac9c1cfc137ff16c21e50a6a3 Reviewed-on: https://gerrit.instructure.com/13641 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
This commit is contained in:
parent
d511e04fee
commit
96ebee6dcc
|
@ -111,7 +111,7 @@ class ConversationsController < ApplicationController
|
|||
# ]
|
||||
def index
|
||||
if request.format == :json
|
||||
conversations = Api.paginate(@conversations_scope, self, request.request_uri.gsub(/(per_)?page=[^&]*(&|\z)/, '').sub(/[&?]\z/, ''))
|
||||
conversations = Api.paginate(@conversations_scope, self, api_v1_conversations_url)
|
||||
# optimize loading the most recent messages for each conversation into a single query
|
||||
ConversationParticipant.preload_latest_messages(conversations, @current_user.id)
|
||||
@conversations_json = conversations.map{ |c| conversation_json(c, @current_user, session, :include_participant_avatars => false, :include_participant_contexts => false, :visible => true) }
|
||||
|
|
|
@ -307,14 +307,16 @@ class CoursesController < ApplicationController
|
|||
|
||||
# If a user_id is passed in, modify the page parameter so that the page
|
||||
# that contains that user is returned.
|
||||
if params[:user_id] && user = users.scoped(:conditions => ["users.id = ?", params[:user_id]]).first
|
||||
# We delete it from params so that it is not maintained in pagination links.
|
||||
user_id = params.delete(:user_id)
|
||||
if user_id.present? && user = users.scoped(:conditions => ["users.id = ?", user_id]).first
|
||||
position_scope = users.scoped(:conditions => ["sortable_name <= ?", user.sortable_name])
|
||||
position = position_scope.count(:select => "users.*", :distinct => true)
|
||||
per_page = Api.per_page_for(self)
|
||||
params[:page] = (position.to_f / per_page.to_f).ceil
|
||||
end
|
||||
|
||||
users = Api.paginate(users, self, api_v1_course_users_path)
|
||||
users = Api.paginate(users, self, api_v1_course_users_url)
|
||||
includes = Array(params[:include])
|
||||
user_json_preloads(users, includes.include?('email'))
|
||||
if includes.include?('enrollments')
|
||||
|
|
|
@ -94,7 +94,7 @@ class SearchController < ApplicationController
|
|||
|
||||
@blank_fallback = !api_request?
|
||||
|
||||
max_results = [params[:per_page].try(:to_i) || 10, 50].min
|
||||
max_results = Api.per_page_for(self)
|
||||
if max_results < 1
|
||||
if !types[:user] || params[:context]
|
||||
max_results = nil # i.e. all results
|
||||
|
@ -132,7 +132,7 @@ class SearchController < ApplicationController
|
|||
def total_pages; nil; end
|
||||
def per_page; #{max_results}; end
|
||||
CODE
|
||||
recipients = Api.paginate(recipients, self, request.request_uri.gsub(/(per_)?page=[^&]*(&|\z)/, '').sub(/[&?]\z/, ''))
|
||||
recipients = Api.paginate(recipients, self, api_v1_search_recipients_url)
|
||||
else
|
||||
if contexts.size <= max_results / 2
|
||||
recipients = contexts + participants
|
||||
|
|
|
@ -824,7 +824,7 @@ ActionController::Routing::Routes.draw do |map|
|
|||
api.get 'conversations/find_recipients', :controller => :conversations, :action => :find_recipients
|
||||
|
||||
api.with_options(:controller => :conversations) do |conversations|
|
||||
conversations.get 'conversations', :action => :index
|
||||
conversations.get 'conversations', :action => :index, :path_name => 'conversations'
|
||||
conversations.post 'conversations', :action => :create
|
||||
conversations.post 'conversations/mark_all_as_read', :action => :mark_all_as_read
|
||||
conversations.get 'conversations/:id', :action => :show
|
||||
|
|
|
@ -1,21 +1,31 @@
|
|||
Pagination
|
||||
==========
|
||||
|
||||
Requests that return multiple items will be paginated to 10 items by default. Further pages
|
||||
can be requested with the `?page` query parameter. You can set a custom per-page amount
|
||||
with the `?per_page` parameter. There is an unspecified limit to how big you can set
|
||||
`per_page` to, so be sure to always check for the `Link` header.
|
||||
Requests that return multiple items will be paginated to 10 items by default.
|
||||
You can set a custom per-page amount with the `?per_page` parameter. There is
|
||||
an unspecified limit to how big you can set `per_page` to, so be sure to always
|
||||
check for the `Link` header.
|
||||
|
||||
To retrieve additional pages, the returned `Link` headers should be used. These
|
||||
links should be treated as opaque. They will be absolute urls that include all
|
||||
parameters necessary to retreive the desired next, previous, first, or last
|
||||
page. The one exception is that if an access_token parameter is sent for
|
||||
authentication, it will not be included in the returned links, and must be
|
||||
re-appended.
|
||||
|
||||
Pagination information is provided in the [Link header](http://www.w3.org/Protocols/9707-link-header.html):
|
||||
|
||||
Link: </courses/:id/discussion_topics.json?page=2&per_page=10>; rel="next",
|
||||
</courses/:id/discussion_topics.json?page=1&per_page=10>; rel="first",
|
||||
</courses/:id/discussion_topics.json?page=5&per_page=10>; rel="last"
|
||||
Link: <https://<canvas>/api/v1/courses/:id/discussion_topics.json?page=2&per_page=10>; rel="next",
|
||||
<https://<canvas>/api/v1/courses/:id/discussion_topics.json?page=1&per_page=10>; rel="first",
|
||||
<https://<canvas>/api/v1/courses/:id/discussion_topics.json?page=5&per_page=10>; rel="last"
|
||||
|
||||
The possible `rel` values are:
|
||||
|
||||
* next - link to the next page of results. None is sent if there is no next page.
|
||||
* prev - link to the previous page of results. None is sent if there is no previous page.
|
||||
* first - link to the first page of results. None is sent if there are no pages.
|
||||
* last - link to the last page of results. None is sent if there are no pages, or if it
|
||||
would be expensive to calculate the number of pages.
|
||||
* next - link to the next page of results.
|
||||
* prev - link to the previous page of results.
|
||||
* first - link to the first page of results.
|
||||
* last - link to the last page of results.
|
||||
|
||||
These will only be included if they are relevant. For example, the first page
|
||||
of results will not contain a rel="prev" link. rel="last" may also be excluded
|
||||
if the total cound is too expensive to compute on each request.
|
||||
|
|
41
lib/api.rb
41
lib/api.rb
|
@ -155,25 +155,38 @@ module Api
|
|||
# a new, paginated collection will be returned
|
||||
def self.paginate(collection, controller, base_url, pagination_args = {})
|
||||
per_page = per_page_for(controller)
|
||||
collection = collection.paginate({ :page => controller.params[:page], :per_page => per_page }.merge(pagination_args))
|
||||
pagination_args.reverse_merge!({ :page => controller.params[:page], :per_page => per_page })
|
||||
collection = collection.paginate(pagination_args)
|
||||
return unless collection.respond_to?(:next_page)
|
||||
links = []
|
||||
base_url += (base_url =~ /\?/ ? '&': '?')
|
||||
template = "<%spage=%s&per_page=#{collection.per_page}>; rel=\"%s\""
|
||||
if collection.next_page
|
||||
links << template % [base_url, collection.next_page, "next"]
|
||||
end
|
||||
if collection.previous_page
|
||||
links << template % [base_url, collection.previous_page, "prev"]
|
||||
end
|
||||
links << template % [base_url, 1, "first"]
|
||||
if !pagination_args[:without_count] && collection.total_pages && collection.total_pages > 1
|
||||
links << template % [base_url, collection.total_pages, "last"]
|
||||
end
|
||||
total_pages = (pagination_args[:without_count] ? nil : collection.total_pages)
|
||||
total_pages = nil if total_pages.to_i <= 1
|
||||
links = build_links(base_url, {
|
||||
:query_parameters => controller.request.query_parameters,
|
||||
:per_page => collection.per_page,
|
||||
:next => collection.next_page,
|
||||
:prev => collection.previous_page,
|
||||
:first => 1,
|
||||
:last => total_pages,
|
||||
})
|
||||
controller.response.headers["Link"] = links.join(',') if links.length > 0
|
||||
collection
|
||||
end
|
||||
|
||||
EXCLUDE_IN_PAGINATION_LINKS = %w(page per_page access_token api_key)
|
||||
def self.build_links(base_url, opts={})
|
||||
links = []
|
||||
base_url += (base_url =~ /\?/ ? '&': '?')
|
||||
qp = opts[:query_parameters] || {}
|
||||
qp = qp.with_indifferent_access.except(*EXCLUDE_IN_PAGINATION_LINKS)
|
||||
base_url += "#{qp.to_query}&" if qp.present?
|
||||
[:next, :prev, :first, :last].each do |k|
|
||||
if opts[k].present?
|
||||
links << "<#{base_url}page=#{opts[k]}&per_page=#{opts[:per_page]}>; rel=\"#{k}\""
|
||||
end
|
||||
end
|
||||
links
|
||||
end
|
||||
|
||||
def media_comment_json(media_object_or_hash)
|
||||
media_object_or_hash = OpenStruct.new(media_object_or_hash) if media_object_or_hash.is_a?(Hash)
|
||||
{
|
||||
|
|
|
@ -110,13 +110,23 @@ describe ConversationsController, :type => :integration do
|
|||
{:controller => 'conversations', :action => 'index', :format => 'json', :scope => 'default', :per_page => '3'})
|
||||
|
||||
json.size.should eql 3
|
||||
response.headers['Link'].should eql(%{</api/v1/conversations.json?scope=default&page=2&per_page=3>; rel="next",</api/v1/conversations.json?scope=default&page=1&per_page=3>; rel="first",</api/v1/conversations.json?scope=default&page=3&per_page=3>; rel="last"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/conversations/ }.should be_true
|
||||
links.all?{ |l| l.scan(/scope=default/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
|
||||
# get the last page
|
||||
json = api_call(:get, "/api/v1/conversations.json?scope=default&page=3&per_page=3",
|
||||
{:controller => 'conversations', :action => 'index', :format => 'json', :scope => 'default', :page => '3', :per_page => '3'})
|
||||
json.size.should eql 1
|
||||
response.headers['Link'].should eql(%{</api/v1/conversations.json?scope=default&page=2&per_page=3>; rel="prev",</api/v1/conversations.json?scope=default&page=1&per_page=3>; rel="first",</api/v1/conversations.json?scope=default&page=3&per_page=3>; rel="last"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/conversations/ }.should be_true
|
||||
links.all?{ |l| l.scan(/scope=default/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
end
|
||||
|
||||
it "should filter conversations by scope" do
|
||||
|
|
|
@ -654,6 +654,18 @@ describe CoursesController, :type => :integration do
|
|||
json.map { |u| u['enrollments'].map { |e| e['type'] } }.flatten.uniq.sort.should == %w{StudentEnrollment TeacherEnrollment}
|
||||
end
|
||||
|
||||
it "maintains query parameters in link headers" do
|
||||
json = api_call(
|
||||
:get,
|
||||
"/api/v1/courses/#{@course1.id}/users.json",
|
||||
{ :controller => 'courses', :action => 'users', :course_id => @course1.id.to_s, :format => 'json' },
|
||||
{ :enrollment_type => 'student', :maintain_params => '1', :per_page => 1 })
|
||||
links = response['Link'].split(",")
|
||||
links.should_not be_empty
|
||||
links.all?{ |l| l =~ /enrollment_type=student/ }.should be_true
|
||||
links.first.scan(/per_page/).length.should == 1
|
||||
end
|
||||
|
||||
it "should not include sis user id or login id for non-admins" do
|
||||
RoleOverride.create!(:context => Account.default, :permission => 'read_sis', :enrollment_type => 'TeacherEnrollment', :enabled => false)
|
||||
student_in_course(:course => @course2, :active_all => true, :name => 'Zombo')
|
||||
|
|
|
@ -387,21 +387,21 @@ describe DiscussionTopicsController, :type => :integration do
|
|||
{:controller => 'discussion_topics', :action => 'index', :format => 'json', :course_id => @course.id.to_s, :per_page => '3'})
|
||||
|
||||
json.length.should == 3
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics?page=2&per_page=3>; rel="next"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/courses\/#{@course.id}\/discussion_topics/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
|
||||
# get the last page
|
||||
json = api_call(:get, "/api/v1/courses/#{@course.id}/discussion_topics.json?page=3&per_page=3",
|
||||
{:controller => 'discussion_topics', :action => 'index', :format => 'json', :course_id => @course.id.to_s, :page => '3', :per_page => '3'})
|
||||
json.length.should == 1
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics?page=2&per_page=3>; rel="prev"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/courses\/#{@course.id}\/discussion_topics/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
end
|
||||
|
||||
it "should work with groups" do
|
||||
|
@ -464,21 +464,21 @@ describe DiscussionTopicsController, :type => :integration do
|
|||
{:controller => 'discussion_topics', :action => 'index', :format => 'json', :group_id => group.id.to_s, :per_page => '3'})
|
||||
|
||||
json.length.should == 3
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/groups/#{group.id}/discussion_topics?page=2&per_page=3>; rel="next"},
|
||||
%{</api/v1/groups/#{group.id}/discussion_topics?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/groups/#{group.id}/discussion_topics?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/groups\/#{group.id}\/discussion_topics/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
|
||||
# get the last page
|
||||
json = api_call(:get, "/api/v1/groups/#{group.id}/discussion_topics.json?page=3&per_page=3",
|
||||
{:controller => 'discussion_topics', :action => 'index', :format => 'json', :group_id => group.id.to_s, :page => '3', :per_page => '3'})
|
||||
json.length.should == 1
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/groups/#{group.id}/discussion_topics?page=2&per_page=3>; rel="prev"},
|
||||
%{</api/v1/groups/#{group.id}/discussion_topics?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/groups/#{group.id}/discussion_topics?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/groups\/#{group.id}\/discussion_topics/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
end
|
||||
|
||||
it "should fulfill module viewed requirements when marking a topic read" do
|
||||
|
@ -720,11 +720,11 @@ describe DiscussionTopicsController, :type => :integration do
|
|||
:course_id => @course.id.to_s, :topic_id => @topic.id.to_s, :per_page => '3' })
|
||||
json.length.should == 3
|
||||
json.map{ |e| e['id'] }.should == entries.last(3).reverse.map{ |e| e.id }
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries?page=2&per_page=3>; rel="next"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/courses\/#{@course.id}\/discussion_topics\/#{@topic.id}\/entries/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
|
||||
# last page
|
||||
json = api_call(
|
||||
|
@ -733,11 +733,11 @@ describe DiscussionTopicsController, :type => :integration do
|
|||
:course_id => @course.id.to_s, :topic_id => @topic.id.to_s, :page => '3', :per_page => '3' })
|
||||
json.length.should == 2
|
||||
json.map{ |e| e['id'] }.should == [entries.first, @entry].map{ |e| e.id }
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries?page=2&per_page=3>; rel="prev"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/courses\/#{@course.id}\/discussion_topics\/#{@topic.id}\/entries/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
end
|
||||
|
||||
it "should only include the first 10 replies for each top-level entry" do
|
||||
|
@ -810,11 +810,11 @@ describe DiscussionTopicsController, :type => :integration do
|
|||
:course_id => @course.id.to_s, :topic_id => @topic.id.to_s, :entry_id => @entry.id.to_s, :per_page => '3' })
|
||||
json.length.should == 3
|
||||
json.map{ |e| e['id'] }.should == replies.last(3).reverse.map{ |e| e.id }
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries/#{@entry.id}/replies?page=2&per_page=3>; rel="next"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries/#{@entry.id}/replies?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries/#{@entry.id}/replies?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/courses\/#{@course.id}\/discussion_topics\/#{@topic.id}\/entries\/#{@entry.id}\/replies/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
|
||||
# last page
|
||||
json = api_call(
|
||||
|
@ -823,11 +823,11 @@ describe DiscussionTopicsController, :type => :integration do
|
|||
:course_id => @course.id.to_s, :topic_id => @topic.id.to_s, :entry_id => @entry.id.to_s, :page => '3', :per_page => '3' })
|
||||
json.length.should == 2
|
||||
json.map{ |e| e['id'] }.should == [replies.first, @reply].map{ |e| e.id }
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries/#{@entry.id}/replies?page=2&per_page=3>; rel="prev"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries/#{@entry.id}/replies?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/courses/#{@course.id}/discussion_topics/#{@topic.id}/entries/#{@entry.id}/replies?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/courses\/#{@course.id}\/discussion_topics\/#{@topic.id}\/entries\/#{@entry.id}\/replies/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -199,21 +199,21 @@ describe ExternalToolsController, :type => :integration do
|
|||
{:controller => 'external_tools', :action => 'index', :format => 'json', :"#{type}_id" => context.id.to_s, :per_page => '3'})
|
||||
|
||||
json.length.should == 3
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/#{type}s/#{context.id}/external_tools?page=2&per_page=3>; rel="next"},
|
||||
%{</api/v1/#{type}s/#{context.id}/external_tools?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/#{type}s/#{context.id}/external_tools?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/#{type}s\/#{context.id}\/external_tools/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3/
|
||||
|
||||
# get the last page
|
||||
json = api_call(:get, "/api/v1/#{type}s/#{context.id}/external_tools.json?page=3&per_page=3",
|
||||
{:controller => 'external_tools', :action => 'index', :format => 'json', :"#{type}_id" => context.id.to_s, :per_page => '3', :page => '3'})
|
||||
json.length.should == 1
|
||||
response.headers['Link'].should == [
|
||||
%{</api/v1/#{type}s/#{context.id}/external_tools?page=2&per_page=3>; rel="prev"},
|
||||
%{</api/v1/#{type}s/#{context.id}/external_tools?page=1&per_page=3>; rel="first"},
|
||||
%{</api/v1/#{type}s/#{context.id}/external_tools?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/#{type}s\/#{context.id}\/external_tools/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3/
|
||||
end
|
||||
|
||||
def tool_with_everything(context, opts={})
|
||||
|
|
|
@ -169,19 +169,19 @@ describe "Files API", :type => :integration do
|
|||
7.times {|i| Attachment.create!(:filename => "test#{i}.txt", :display_name => "test#{i}.txt", :uploaded_data => StringIO.new('file'), :folder => @root, :context => @course) }
|
||||
json = api_call(:get, "/api/v1/folders/#{@root.id}/files?per_page=3", @files_path_options.merge(:id => @root.id.to_param, :per_page => '3'), {})
|
||||
json.length.should == 3
|
||||
response.headers['Link'].should == [
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/files?page=2&per_page=3>; rel="next"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/files?page=1&per_page=3>; rel="first"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/files?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/folders\/#{@root.id}\/files/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3/
|
||||
|
||||
json = api_call(:get, "/api/v1/folders/#{@root.id}/files?per_page=3&page=3", @files_path_options.merge(:id => @root.id.to_param, :per_page => '3', :page => '3'), {})
|
||||
json.length.should == 1
|
||||
response.headers['Link'].should == [
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/files?page=2&per_page=3>; rel="prev"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/files?page=1&per_page=3>; rel="first"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/files?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/folders\/#{@root.id}\/files/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3/
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -84,19 +84,19 @@ describe "Folders API", :type => :integration do
|
|||
7.times {|i| @root.sub_folders.create!(:name => "folder#{i}", :context => @course) }
|
||||
json = api_call(:get, @folders_path + "/#{@root.id}/folders?per_page=3", @folders_path_options.merge(:per_page => '3'), {})
|
||||
json.length.should == 3
|
||||
response.headers['Link'].should == [
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/folders?page=2&per_page=3>; rel="next"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/folders?page=1&per_page=3>; rel="first"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/folders?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/folders\/#{@root.id}\/folders/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
|
||||
json = api_call(:get, @folders_path + "/#{@root.id}/folders?per_page=3&page=3", @folders_path_options.merge(:per_page => '3', :page => '3'), {})
|
||||
json.length.should == 1
|
||||
response.headers['Link'].should == [
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/folders?page=2&per_page=3>; rel="prev"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/folders?page=1&per_page=3>; rel="first"},
|
||||
%{<http://www.example.com/api/v1/folders/#{@root.id}/folders?page=3&per_page=3>; rel="last"}
|
||||
].join(',')
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/folders\/#{@root.id}\/folders/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=3&per_page=3>/
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -267,13 +267,23 @@ describe SearchController, :type => :integration do
|
|||
json = api_call(:get, "/api/v1/search/recipients.json?search=cletus&type=user&per_page=3",
|
||||
{:controller => 'search', :action => 'recipients', :format => 'json', :search => 'cletus', :type => 'user', :per_page => '3'})
|
||||
json.size.should eql 3
|
||||
response.headers['Link'].should eql(%{</api/v1/search/recipients.json?search=cletus&type=user&page=2&per_page=3>; rel="next",</api/v1/search/recipients.json?search=cletus&type=user&page=1&per_page=3>; rel="first"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/search\/recipients/ }.should be_true
|
||||
links.all?{ |l| l.scan(/search=cletus/).size == 1 }.should be_true
|
||||
links.all?{ |l| l.scan(/type=user/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
|
||||
# get the next page
|
||||
json = api_call(:get, "/api/v1/search/recipients.json?search=cletus&type=user&page=2&per_page=3",
|
||||
{:controller => 'search', :action => 'recipients', :format => 'json', :search => 'cletus', :type => 'user', :page => '2', :per_page => '3'})
|
||||
json.size.should eql 1
|
||||
response.headers['Link'].should eql(%{</api/v1/search/recipients.json?search=cletus&type=user&page=1&per_page=3>; rel="prev",</api/v1/search/recipients.json?search=cletus&type=user&page=1&per_page=3>; rel="first"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/search\/recipients/ }.should be_true
|
||||
links.all?{ |l| l.scan(/search=cletus/).size == 1 }.should be_true
|
||||
links.all?{ |l| l.scan(/type=user/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
end
|
||||
|
||||
it "should allow fetching all users iff a context is specified" do
|
||||
|
@ -284,7 +294,12 @@ describe SearchController, :type => :integration do
|
|||
json = api_call(:get, "/api/v1/search/recipients.json?search=cletus&type=user&per_page=-1",
|
||||
{:controller => 'search', :action => 'recipients', :format => 'json', :search => 'cletus', :type => 'user', :per_page => '-1'})
|
||||
json.size.should eql 10
|
||||
response.headers['Link'].should eql(%{</api/v1/search/recipients.json?search=cletus&type=user&page=2&per_page=10>; rel="next",</api/v1/search/recipients.json?search=cletus&type=user&page=1&per_page=10>; rel="first"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/search\/recipients/ }.should be_true
|
||||
links.all?{ |l| l.scan(/search=cletus/).size == 1 }.should be_true
|
||||
links.all?{ |l| l.scan(/type=user/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=10>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=10>/
|
||||
|
||||
json = api_call(:get, "/api/v1/search/recipients.json?search=cletus&type=user&context=course_#{@course.id}&per_page=-1",
|
||||
{:controller => 'search', :action => 'recipients', :format => 'json', :search => 'cletus', :context => "course_#{@course.id}", :type => 'user', :per_page => '-1'})
|
||||
|
@ -301,13 +316,23 @@ describe SearchController, :type => :integration do
|
|||
json = api_call(:get, "/api/v1/search/recipients.json?search=ofcourse&type=context&per_page=3",
|
||||
{:controller => 'search', :action => 'recipients', :format => 'json', :search => 'ofcourse', :type => 'context', :per_page => '3'})
|
||||
json.size.should eql 3
|
||||
response.headers['Link'].should eql(%{</api/v1/search/recipients.json?search=ofcourse&type=context&page=2&per_page=3>; rel="next",</api/v1/search/recipients.json?search=ofcourse&type=context&page=1&per_page=3>; rel="first"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/search\/recipients/ }.should be_true
|
||||
links.all?{ |l| l.scan(/search=ofcourse/).size == 1 }.should be_true
|
||||
links.all?{ |l| l.scan(/type=context/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=2&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
|
||||
# get the next page
|
||||
json = api_call(:get, "/api/v1/search/recipients.json?search=ofcourse&type=context&page=2&per_page=3",
|
||||
{:controller => 'search', :action => 'recipients', :format => 'json', :search => 'ofcourse', :type => 'context', :page => '2', :per_page => '3'})
|
||||
json.size.should eql 1
|
||||
response.headers['Link'].should eql(%{</api/v1/search/recipients.json?search=ofcourse&type=context&page=1&per_page=3>; rel="prev",</api/v1/search/recipients.json?search=ofcourse&type=context&page=1&per_page=3>; rel="first"})
|
||||
links = response.headers['Link'].split(",")
|
||||
links.all?{ |l| l =~ /api\/v1\/search\/recipients/ }.should be_true
|
||||
links.all?{ |l| l.scan(/search=ofcourse/).size == 1 }.should be_true
|
||||
links.all?{ |l| l.scan(/type=context/).size == 1 }.should be_true
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=1&per_page=3>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=3>/
|
||||
end
|
||||
|
||||
it "should allow fetching all contexts" do
|
||||
|
|
|
@ -529,4 +529,63 @@ describe Api do
|
|||
res.should == html
|
||||
end
|
||||
end
|
||||
|
||||
context ".build_links" do
|
||||
it "should not build links if not pagination is provided" do
|
||||
Api.build_links("www.example.com").should be_empty
|
||||
end
|
||||
|
||||
it "should not build links for empty pages" do
|
||||
Api.build_links("www.example.com/", {
|
||||
:per_page => 10,
|
||||
:next => "",
|
||||
:prev => "",
|
||||
:first => "",
|
||||
:last => "",
|
||||
}).should be_empty
|
||||
end
|
||||
|
||||
it "should build next, prev, first, and last links if provided" do
|
||||
links = Api.build_links("www.example.com/", {
|
||||
:per_page => 10,
|
||||
:next => 4,
|
||||
:prev => 2,
|
||||
:first => 1,
|
||||
:last => 10,
|
||||
})
|
||||
links.all?{ |l| l =~ /www.example.com\/\?/ }.should be_true
|
||||
links.find{ |l| l.match(/rel="next"/)}.should =~ /page=4&per_page=10>/
|
||||
links.find{ |l| l.match(/rel="prev"/)}.should =~ /page=2&per_page=10>/
|
||||
links.find{ |l| l.match(/rel="first"/)}.should =~ /page=1&per_page=10>/
|
||||
links.find{ |l| l.match(/rel="last"/)}.should =~ /page=10&per_page=10>/
|
||||
end
|
||||
|
||||
it "should maintain query parameters" do
|
||||
links = Api.build_links("www.example.com/", {
|
||||
:query_parameters => { :search => "hihi" },
|
||||
:per_page => 10,
|
||||
:next => 2,
|
||||
})
|
||||
links.first.should == "<www.example.com/?search=hihi&page=2&per_page=10>; rel=\"next\""
|
||||
end
|
||||
|
||||
it "should maintain array query parameters" do
|
||||
links = Api.build_links("www.example.com/", {
|
||||
:query_parameters => { :include => ["enrollments"] },
|
||||
:per_page => 10,
|
||||
:next => 2,
|
||||
})
|
||||
qs = "#{CGI.escape("include[]")}=enrollments"
|
||||
links.first.should == "<www.example.com/?#{qs}&page=2&per_page=10>; rel=\"next\""
|
||||
end
|
||||
|
||||
it "should not include certain sensitive params in the link headers" do
|
||||
links = Api.build_links("www.example.com/", {
|
||||
:query_parameters => { :access_token => "blah", :api_key => "xxx", :page => 3, :per_page => 10 },
|
||||
:per_page => 10,
|
||||
:next => 4,
|
||||
})
|
||||
links.first.should == "<www.example.com/?page=4&per_page=10>; rel=\"next\""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue