wiki pages CRUD API

fixes CNVS-5279

test plan:
 - see API documentation to create/retrieve/update/delete pages
 - exercise endpoints, including
   - CRUD functionality (above)
   - test renaming page
   - test publishing / unpublishing
   - test creating the front page in a new course or group

Change-Id: I5d3b36615b7bdbfda0d4781db14cd7d49d32eba1
Reviewed-on: https://gerrit.instructure.com/19952
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
Product-Review: Bracken Mosbacker <bracken@instructure.com>
QA-Review: Adam Phillipps <adam@instructure.com>
This commit is contained in:
Jeremy Stanley 2013-04-19 13:55:00 -06:00
parent 83e192a7e8
commit 8bdfdd3103
7 changed files with 593 additions and 164 deletions

View File

@ -0,0 +1,236 @@
#
# 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/>.
#
# @API Pages
#
# Pages are rich content associated with Courses and Groups in Canvas.
# The Pages API allows you to create, retrieve, update, and delete ages.
#
# @object Page
# {
# // the unique locator for the page
# url: "my-page-title",
#
# // the title of the page
# title: "My Page Title",
#
# // the creation date for the page
# created_at: "2012-08-06T16:46:33-06:00",
#
# // the date the page was last updated
# updated_at: "2012-08-08T14:25:20-06:00",
#
# // whether this page is hidden from students
# // (note: students will never see this true; pages hidden from them will be omitted from results)
# hide_from_students: false,
#
# // roles allowed to edit the page; comma-separated list comprising a combination of
# // 'teachers', 'students', and/or 'public'
# // if not supplied, course defaults are used
# editing_roles: "teachers,students",
#
# // the User who last edited the page
# // (this may not be present if the page was imported from another system)
# last_edited_by: {
# id: 133,
# display_name: "Rey del Pueblo",
# avatar_image_url: "https://canvas.example.com/images/thumbnails/bm90aGluZyBoZXJl",
# html_url: "https://canvas.example.com/courses/789/users/133"
# },
#
# // the page content, in HTML
# // (present when requesting a single page; omitted when listing pages)
# body: "<p>Page Content</p>",
#
# // whether the page is published
# published: true
# }
class WikiPagesApiController < ApplicationController
before_filter :require_context
before_filter :get_wiki_page, :except => [:create, :index]
include Api::V1::WikiPage
# @API List pages
#
# List the wiki pages associated with a course or group
#
# @argument sort [optional] Sort results by this field: one of 'title', 'created_at', or 'updated_at'
# @argument order [optional] The sorting order: 'asc' (default) or 'desc'
#
# @example_request
# curl -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages?sort=title&order=asc
#
# @returns [Page]
def index
if authorized_action(@context.wiki, @current_user, :read)
pages_route = polymorphic_url([:api_v1, @context, :wiki_pages])
# omit body from selection, since it's not included in index results
scope = @context.wiki.wiki_pages.
select(%w(wiki_pages.wiki_id wiki_pages.url wiki_pages.title wiki_pages.created_at wiki_pages.updated_at wiki_pages.hide_from_students wiki_pages.editing_roles wiki_pages.workflow_state wiki_pages.user_id)).
includes(:user)
scope = @context.grants_right?(@current_user, session, :view_unpublished_items) ? scope.not_deleted : scope.active
scope = scope.visible_to_students unless @context.grants_right?(@current_user, session, :view_hidden_items)
order_clause = case params[:sort]
when 'title'
WikiPage.title_order_by_clause
when 'created_at'
'wiki_pages.created_at'
when 'updated_at'
'wiki_pages.updated_at'
else
'wiki_pages.id'
end
order_clause += ' DESC' if params[:order] == 'desc'
scope = scope.order(order_clause)
wiki_pages = Api.paginate(scope, self, pages_route)
render :json => wiki_pages_json(wiki_pages, @current_user, session)
end
end
# @API Show page
#
# Retrieve the content of a wiki page
#
# @argument url the unique identifier for a page. Use 'front-page' to retrieve the front page of the wiki.
#
# @example_request
# curl -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages/front-page
#
# @returns Page
def show
if authorized_action(@page, @current_user, :read)
@page.increment_view_count(@current_user, @context)
log_asset_access(@page, "wiki", @wiki)
render :json => wiki_page_json(@page, @current_user, session)
end
end
# @API Create page
#
# Create a new wiki page
#
# @argument wiki_page[title] the title for the new page.
# @argument wiki_page[body] the content for the new page.
# @argument wiki_page[hide_from_students] [boolean] whether the page should be hidden from students.
# @argument wiki_page[notify_of_update] [boolean] whether participants should be notified when this page changes.
# @argument wiki_page[published] [optional] [boolean] whether the page is published (true) or draft state (false).
#
# @example_request
# curl -X POST -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages?wiki_page[title]=New+page&wiki_page[body]=New+body+text
#
# @returns Page
def create
@page = @context.wiki.wiki_pages.build
if authorized_action(@page, @current_user, :update)
attrs_to_update = process_update_params
if @page.update_attributes(attrs_to_update)
log_asset_access(@page, "wiki", @wiki, 'participate')
render :json => wiki_page_json(@page, @current_user, session)
else
render :json => @page.errors.to_json, :status => :bad_request
end
end
end
# @API Update page
#
# Update the title or contents of a wiki page
#
# @argument url the unique identifier for a page.
# @argument wiki_page[title] [optional] the new title for the page.
# NOTE: changing a page's title will change its url. The updated url will be returned in the result.
# @argument wiki_page[body] [optional] the new content for the page.
# @argument wiki_page[hide_from_students] [optional] boolean; whether the page should be hidden from students.
# @argument wiki_page[notify_of_update] [optional] [boolean] notify participants that the wiki page has been changed.
# @argument wiki_page[published] [optional] [boolean] whether the page is published (true) or draft state (false)
#
# @example_request
# curl -X PUT -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages/the-page-url?wiki_page[body]=Updated+body+text
#
# @returns Page
def update
if authorized_action(@page, @current_user, :update_content)
attrs_to_update = process_update_params
if @page.update_attributes(attrs_to_update)
log_asset_access(@page, "wiki", @wiki, 'participate')
@page.context_module_action(@current_user, @context, :contributed)
render :json => wiki_page_json(@page, @current_user, session)
else
render :json => @page.errors.to_json, :status => :bad_request
end
end
end
# @API Delete page
#
# Delete a wiki page
#
# @argument url the unique identifier for a page.
#
# @example_request
# curl -X DELETE -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages/the-page-url
#
# @returns Page
def destroy
if authorized_action(@page, @current_user, :delete)
@page.workflow_state = 'deleted'
@page.save!
render :json => wiki_page_json(@page, @current_user, session)
end
end
protected
def get_wiki_page
@wiki = @context.wiki
@page = @wiki.wiki_pages.not_deleted.find_by_url!(params[:url])
end
def process_update_params
page_params = params[:wiki_page] || {}
if @page.grants_right?(@current_user, session, :update)
if page_params.has_key? :published
new_state = value_to_boolean(page_params.delete(:published)) ? 'active' : 'unpublished'
@page.workflow_state = new_state
end
roles = page_params[:editing_roles]
if roles.present?
page_params[:editing_roles] = roles.split(',').map(&:strip).reject{|role| !%w(teachers students public).include?(role)}.join(',')
end
else
# editing_roles only allow changing content, not title or attributes
page_params.slice!(:body)
end
page_params[:user_id] = @current_user.id if @current_user
page_params
end
end

View File

@ -15,42 +15,12 @@
# You should have received a copy of the GNU Affero General Public License along # 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/>. # with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# @API Pages
#
# Pages are rich content associated with Courses and Groups in Canvas.
# The Pages API allows this content to be enumerated and retrieved.
#
# @object Page
# {
# // the unique locator for the page
# url: "my-page-title",
#
# // the title of the page
# title: "My Page Title",
#
# // the creation date for the page
# created_at: "2012-08-06T16:46:33-06:00",
#
# // the date the page was last updated
# updated_at: "2012-08-08T14:25:20-06:00",
#
# // whether this page is hidden from students
# // (note: students will never see this true; pages hidden from them will be omitted from results)
# hide_from_students: false,
#
# // the page content, in HTML
# // (present when requesting a single page; omitted when listing pages)
# body: "<p>Page Content</p>"
# }
class WikiPagesController < ApplicationController class WikiPagesController < ApplicationController
before_filter :require_context before_filter :require_context
before_filter :get_wiki_page, :except => [:index, :api_index, :api_show] before_filter :get_wiki_page, :except => [:index]
add_crumb(proc { t '#crumbs.wiki_pages', "Pages"}) { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_wiki_pages_url } add_crumb(proc { t '#crumbs.wiki_pages', "Pages"}) { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_wiki_pages_url }
before_filter { |c| c.active_tab = "pages" } before_filter { |c| c.active_tab = "pages" }
include Api::V1::WikiPage
def show def show
@editing = true if Canvas::Plugin.value_to_boolean(params[:edit]) @editing = true if Canvas::Plugin.value_to_boolean(params[:edit])
if @page.deleted? && !@page.grants_right?(@current_user, session, :update) && @page.url != 'front-page' if @page.deleted? && !@page.grants_right?(@current_user, session, :update) && @page.url != 'front-page'
@ -60,7 +30,8 @@ class WikiPagesController < ApplicationController
end end
if is_authorized_action?(@page, @current_user, :read) if is_authorized_action?(@page, @current_user, :read)
add_crumb(@page.title) add_crumb(@page.title)
update_view_count(@page) @page.increment_view_count(@current_user, @context)
log_asset_access(@page, "wiki", @wiki)
respond_to do |format| respond_to do |format|
format.html {render :action => "show" } format.html {render :action => "show" }
format.json {render :json => @page.to_json } format.json {render :json => @page.to_json }
@ -75,53 +46,6 @@ class WikiPagesController < ApplicationController
redirect_to named_context_url(@context, :context_wiki_page_url, 'front-page') redirect_to named_context_url(@context, :context_wiki_page_url, 'front-page')
end end
# @API List pages
#
# Lists the wiki pages associated with a course or group.
#
# @example_request
# curl -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages
#
# @returns [Page]
def api_index
if authorized_action(@context.wiki, @current_user, :read)
pages_route = polymorphic_url([:api_v1, @context, :wiki_pages])
scope = @context.wiki.wiki_pages.order_by_id
if @context.grants_right?(@current_user, session, :view_unpublished_items)
scope = scope.not_deleted
else
scope = scope.active
end
if !@context.grants_right?(@current_user, session, :view_hidden_items)
scope = scope.visible_to_students
end
wiki_pages = Api.paginate(scope, self, pages_route)
render :json => wiki_pages_json(wiki_pages, @current_user, session)
end
end
# @API Show page
#
# Retrieves the content of a wiki page.
#
# @argument url the unique identifier for a page. Use 'front-page' to retrieve the front page of the wiki.
#
# @example_request
# curl -H 'Authorization: Bearer <token>' \
# https://<canvas>/api/v1/courses/123/pages/front-page
#
# @returns Page
def api_show
# not using get_wiki_page since this API is read-only for now
@wiki = @context.wiki
@page = @wiki.wiki_pages.not_deleted.find_by_url!(params[:url])
if authorized_action(@page, @current_user, :read)
update_view_count(@page)
render :json => wiki_page_json(@page, @current_user, session)
end
end
def update def update
if authorized_action(@page, @current_user, :update_content) if authorized_action(@page, @current_user, :update_content)
unless @page.grants_right?(@current_user, session, :update) unless @page.grants_right?(@current_user, session, :update)
@ -188,15 +112,5 @@ class WikiPagesController < ApplicationController
end end
res res
end end
def update_view_count(page)
unless page.new_record?
page.with_versioning(false) do |p|
p.context_module_action(@current_user, @context, :read)
WikiPage.connection.execute("UPDATE wiki_pages SET view_count=COALESCE(view_count, 0) + 1 WHERE id=#{p.id}")
end
log_asset_access(page, "wiki", @wiki)
end
end
end end

View File

@ -60,6 +60,10 @@ class WikiPage < ActiveRecord::Base
end end
end end
def self.title_order_by_clause
best_unicode_collation_key('wiki_pages.title')
end
def ensure_unique_url def ensure_unique_url
url_attribute = self.class.url_attribute url_attribute = self.class.url_attribute
base_url = self.send(url_attribute) base_url = self.send(url_attribute)
@ -142,7 +146,7 @@ class WikiPage < ActiveRecord::Base
end end
def notify_of_update=(val) def notify_of_update=(val)
@wiki_page_changed = (val == '1' || val == true) @wiki_page_changed = Canvas::Plugin.value_to_boolean(val)
end end
def notify_of_update def notify_of_update
@ -495,4 +499,14 @@ class WikiPage < ActiveRecord::Base
def self.comments_enabled? def self.comments_enabled?
!Rails.env.production? !Rails.env.production?
end end
def increment_view_count(user, context = nil)
unless self.new_record?
self.with_versioning(false) do |p|
context ||= p.context
p.connection.execute("UPDATE wiki_pages SET view_count=COALESCE(view_count, 0) + 1 WHERE id=#{p.id}")
p.context_module_action(user, context, :read)
end
end
end
end end

View File

@ -1045,11 +1045,17 @@ ActionController::Routing::Routes.draw do |map|
favorites.delete "users/self/favorites/courses", :action => :reset_course_favorites favorites.delete "users/self/favorites/courses", :action => :reset_course_favorites
end end
api.with_options(:controller => :wiki_pages) do |wiki_pages| api.with_options(:controller => :wiki_pages_api) do |wiki_pages|
wiki_pages.get "courses/:course_id/pages", :action => :api_index, :path_name => 'course_wiki_pages' wiki_pages.get "courses/:course_id/pages", :action => :index, :path_name => 'course_wiki_pages'
wiki_pages.get "groups/:group_id/pages", :action => :api_index, :path_name => 'group_wiki_pages' wiki_pages.get "groups/:group_id/pages", :action => :index, :path_name => 'group_wiki_pages'
wiki_pages.get "courses/:course_id/pages/:url", :action => :api_show, :path_name => 'course_wiki_page' wiki_pages.get "courses/:course_id/pages/:url", :action => :show, :path_name => 'course_wiki_page'
wiki_pages.get "groups/:group_id/pages/:url", :action => :api_show, :path_name => 'group_wiki_page' wiki_pages.get "groups/:group_id/pages/:url", :action => :show, :path_name => 'group_wiki_page'
wiki_pages.post "courses/:course_id/pages", :action => :create
wiki_pages.post "groups/:group_id/pages", :action => :create
wiki_pages.put "courses/:course_id/pages/:url", :action => :update
wiki_pages.put "groups/:group_id/pages/:url", :action => :update
wiki_pages.delete "courses/:course_id/pages/:url", :action => :destroy
wiki_pages.delete "groups/:group_id/pages/:url", :action => :destroy
end end
api.with_options(:controller => :context_modules_api) do |context_modules| api.with_options(:controller => :context_modules_api) do |context_modules|

View File

@ -20,15 +20,18 @@ module Api::V1::WikiPage
include Api::V1::Json include Api::V1::Json
include Api::V1::User include Api::V1::User
WIKI_PAGE_JSON_ATTRS = %w(url title created_at updated_at hide_from_students) WIKI_PAGE_JSON_ATTRS = %w(url title created_at updated_at hide_from_students editing_roles)
def wiki_page_json(wiki_page, current_user, session) def wiki_page_json(wiki_page, current_user, session, include_body = true)
hash = api_json(wiki_page, current_user, session, :only => WIKI_PAGE_JSON_ATTRS) hash = api_json(wiki_page, current_user, session, :only => WIKI_PAGE_JSON_ATTRS)
hash['body'] = api_user_content(wiki_page.body) hash['editing_roles'] ||= 'teachers'
hash['body'] = api_user_content(wiki_page.body) if include_body
hash['last_edited_by'] = user_display_json(wiki_page.user, wiki_page.context) if wiki_page.user
hash['published'] = wiki_page.active?
hash hash
end end
def wiki_pages_json(wiki_pages, current_user, session) def wiki_pages_json(wiki_pages, current_user, session)
wiki_pages.map { |page| api_json(page, current_user, session, :only => WIKI_PAGE_JSON_ATTRS) } wiki_pages.map { |page| wiki_page_json(page, current_user, session, false) }
end end
end end

View File

@ -218,7 +218,7 @@ describe UserContent, :type => :integration do
@wiki_page.workflow_state = 'active' @wiki_page.workflow_state = 'active'
@wiki_page.save! @wiki_page.save!
api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@wiki_page.url}", api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@wiki_page.url}",
{ :controller => 'wiki_pages', :action => 'api_show', { :controller => 'wiki_pages_api', :action => 'show',
:format => 'json', :course_id => @course.id.to_s, :url => @wiki_page.url }) :format => 'json', :course_id => @course.id.to_s, :url => @wiki_page.url })
end end
@ -244,7 +244,7 @@ describe UserContent, :type => :integration do
@wiki_page.save! @wiki_page.save!
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@wiki_page.url}", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@wiki_page.url}",
{ :controller => 'wiki_pages', :action => 'api_show', { :controller => 'wiki_pages_api', :action => 'show',
:format => 'json', :course_id => @course.id.to_s, :url => @wiki_page.url }) :format => 'json', :course_id => @course.id.to_s, :url => @wiki_page.url })
doc = Nokogiri::HTML::DocumentFragment.parse(json['body']) doc = Nokogiri::HTML::DocumentFragment.parse(json['body'])
doc.css('a').collect { |att| att['data-api-endpoint'] }.should == [ doc.css('a').collect { |att| att['data-api-endpoint'] }.should == [
@ -281,7 +281,7 @@ describe UserContent, :type => :integration do
@wiki_page.save! @wiki_page.save!
json = api_call(:get, "/api/v1/groups/#{@group.id}/pages/#{@wiki_page.url}", json = api_call(:get, "/api/v1/groups/#{@group.id}/pages/#{@wiki_page.url}",
{ :controller => 'wiki_pages', :action => 'api_show', { :controller => 'wiki_pages_api', :action => 'show',
:format => 'json', :group_id => @group.id.to_s, :url => @wiki_page.url }) :format => 'json', :group_id => @group.id.to_s, :url => @wiki_page.url })
doc = Nokogiri::HTML::DocumentFragment.parse(json['body']) doc = Nokogiri::HTML::DocumentFragment.parse(json['body'])
doc.css('a').collect { |att| att['data-api-endpoint'] }.should == [ doc.css('a').collect { |att| att['data-api-endpoint'] }.should == [

View File

@ -33,51 +33,231 @@ describe "Pages API", :type => :integration do
course_with_teacher(:course => @course, :active_all => true) course_with_teacher(:course => @course, :active_all => true)
end end
it "should list pages, including hidden ones" do describe "index" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages", it "should list pages, including hidden ones" do
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}") json = api_call(:get, "/api/v1/courses/#{@course.id}/pages",
json.should == [{"hide_from_students" => false, "url" => @front_page.url, "created_at" => @front_page.created_at.as_json, "updated_at" => @front_page.updated_at.as_json, "title" => @front_page.title}, :controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param)
{"hide_from_students" => true, "url" => @hidden_page.url, "created_at" => @hidden_page.created_at.as_json, "updated_at" => @hidden_page.updated_at.as_json, "title" => @hidden_page.title}] json.map {|entry| entry.slice(*%w(hide_from_students url created_at updated_at title))}.should ==
[{"hide_from_students" => false, "url" => @front_page.url, "created_at" => @front_page.created_at.as_json, "updated_at" => @front_page.updated_at.as_json, "title" => @front_page.title},
{"hide_from_students" => true, "url" => @hidden_page.url, "created_at" => @hidden_page.created_at.as_json, "updated_at" => @hidden_page.updated_at.as_json, "title" => @hidden_page.title}]
end
it "should paginate" do
2.times { |i| @wiki.wiki_pages.create!(:title => "New Page #{i}") }
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?per_page=2",
:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param, :per_page => "2")
json.size.should == 2
urls = json.collect{ |page| page['url'] }
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?per_page=2&page=2",
:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param, :per_page => "2", :page => "2")
json.size.should == 2
urls += json.collect{ |page| page['url'] }
urls.should == @wiki.wiki_pages.sort_by(&:id).collect(&:url)
end
describe "sorting" do
it "should sort by title (case-insensitive)" do
@wiki.wiki_pages.create! :title => 'gIntermediate Page'
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?sort=title",
:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param,
:sort=>'title')
json.map {|page|page['title']}.should == ['Front Page', 'gIntermediate Page', 'Hidden Page']
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?sort=title&order=desc",
:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param,
:sort=>'title', :order=>'desc')
json.map {|page|page['title']}.should == ['Hidden Page', 'gIntermediate Page', 'Front Page']
end
it "should sort by created_at" do
@hidden_page.update_attribute(:created_at, 1.hour.ago)
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?sort=created_at&order=asc",
:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param,
:sort=>'created_at', :order=>'asc')
json.map {|page|page['url']}.should == [@hidden_page.url, @front_page.url]
end
it "should sort by updated_at" do
Timecop.freeze(1.hour.ago) { @hidden_page.touch }
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?sort=updated_at&order=desc",
:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>@course.to_param,
:sort=>'updated_at', :order=>'desc')
json.map {|page|page['url']}.should == [@front_page.url, @hidden_page.url]
end
end
end
describe "show" do
include Api::V1::User
def avatar_url_for_user(user, *a)
"http://www.example.com/images/messages/avatar-50.png"
end
def blank_fallback
nil
end
before do
@teacher.short_name = 'the teacher'
@teacher.save!
@hidden_page.user_id = @teacher.id
@hidden_page.save!
end
it "should retrieve page content and attributes" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
:controller=>"wiki_pages_api", :action=>"show", :format=>"json", :course_id=>"#{@course.id}", :url=>@hidden_page.url)
expected = { "hide_from_students" => true,
"editing_roles" => "teachers",
"last_edited_by" => user_display_json(@teacher, @course).stringify_keys!,
"url" => @hidden_page.url,
"created_at" => @hidden_page.created_at.as_json,
"updated_at" => @hidden_page.updated_at.as_json,
"title" => @hidden_page.title,
"body" => @hidden_page.body,
"published" => true }
json.should == expected
end
end
describe "create" do
it "should require a title" do
api_call(:post, "/api/v1/courses/#{@course.id}/pages",
{ :controller => 'wiki_pages_api', :action => 'create', :format => 'json', :course_id => @course.to_param },
{}, {}, {:expected_status => 400})
end
it "should create a new page" do
json = api_call(:post, "/api/v1/courses/#{@course.id}/pages",
{ :controller => 'wiki_pages_api', :action => 'create', :format => 'json', :course_id => @course.to_param },
{ :wiki_page => { :title => 'New Wiki Page!', :body => 'hello new page' }})
page = @course.wiki.wiki_pages.find_by_url!(json['url'])
page.title.should == 'New Wiki Page!'
page.url.should == 'new-wiki-page'
page.body.should == 'hello new page'
page.user_id.should == @teacher.id
end
it "should create a new page in published state" do
json = api_call(:post, "/api/v1/courses/#{@course.id}/pages",
{ :controller => 'wiki_pages_api', :action => 'create', :format => 'json', :course_id => @course.to_param },
{ :wiki_page => { :published => true, :title => 'New Wiki Page!', :body => 'hello new page' }})
page = @course.wiki.wiki_pages.find_by_url!(json['url'])
page.should be_active
json['published'].should be_true
end
it "should create a new page in unpublished state" do
json = api_call(:post, "/api/v1/courses/#{@course.id}/pages",
{ :controller => 'wiki_pages_api', :action => 'create', :format => 'json', :course_id => @course.to_param },
{ :wiki_page => { :published => false, :title => 'New Wiki Page!', :body => 'hello new page' }})
page = @course.wiki.wiki_pages.find_by_url!(json['url'])
page.should be_unpublished
json['published'].should be_false
end
end end
it "should paginate" do describe "update" do
2.times { |i| @wiki.wiki_pages.create!(:title => "New Page #{i}") } it "should update page content and attributes" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?per_page=2", api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}", :per_page=>"2") { :controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
json.size.should == 2 :url => @hidden_page.url },
urls = json.collect{ |page| page['url'] } { :wiki_page => { :title => 'No Longer Hidden Page', :hide_from_students => false,
:body => 'Information wants to be free' }})
@hidden_page.reload
@hidden_page.should be_active
@hidden_page.hide_from_students.should be_false
@hidden_page.title.should == 'No Longer Hidden Page'
@hidden_page.body.should == 'Information wants to be free'
@hidden_page.user_id.should == @teacher.id
end
context "with unpublished page" do
before do
@hidden_page.workflow_state = 'unpublished'
@hidden_page.save!
end
it "should publish a page with published=true" do
json = api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}?wiki_page[published]=true",
:controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @hidden_page.url, :wiki_page => {'published' => 'true'})
json['published'].should be_true
@hidden_page.reload.should be_active
end
it "should not publish a page otherwise" do
json = api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
:controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @hidden_page.url)
json['published'].should be_false
@hidden_page.reload.should be_unpublished
end
end
it "should unpublish a page" do
json = api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}?wiki_page[published]=false",
:controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @hidden_page.url, :wiki_page => {'published' => 'false'})
json['published'].should be_false
@hidden_page.reload.should be_unpublished
end
it "should sanitize page content" do
api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
{ :controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @hidden_page.url },
{ :wiki_page => { :body => "<p>lolcats</p><script>alert('what')</script>" }})
@hidden_page.reload
@hidden_page.body.should == "<p>lolcats</p>alert('what')"
end
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?page=2&per_page=2", it "should clean editing_roles" do
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}", :page => "2", :per_page=>"2") api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
json.size.should == 2 { :controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
urls += json.collect{ |page| page['url'] } :url => @hidden_page.url },
{ :wiki_page => { :editing_roles => 'teachers, chimpanzees, students' }})
@hidden_page.reload
@hidden_page.editing_roles.should == 'teachers,students'
end
urls.should == @wiki.wiki_pages.sort_by(&:id).collect(&:url) it "should 404 if the page doesn't exist" do
api_call(:put, "/api/v1/courses/#{@course.id}/pages/nonexistent-url?title=renamed",
{ :controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => 'nonexistent-url', :title => 'renamed' }, {}, {}, { :expected_status => 404 })
end
describe "notify_of_update" do
before do
@front_page.update_attribute(:created_at, 1.hour.ago)
@hidden_page.update_attribute(:created_at, 1.hour.ago)
@notification = Notification.create! :name => "Updated Wiki Page"
@teacher.communication_channels.create(:path => "teacher@instructure.com").confirm!
@teacher.email_channel.notification_policies.
find_or_create_by_notification_id(@notification.id).
update_attribute(:frequency, 'immediately')
end
it "should notify iff the notify_on_update flag is sent" do
api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@front_page.url}?wiki_page[body]=updated+front+page",
:controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @front_page.url, :wiki_page => { "body" => "updated front page" })
api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}?wiki_page[body]=updated+hidden+page&wiki_page[notify_of_update]=true",
:controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @hidden_page.url, :wiki_page => { "body" => "updated hidden page", "notify_of_update" => 'true' })
@teacher.messages.map(&:context_id).should == [@hidden_page.id]
end
end
end end
it "should retrieve page content" do describe "delete" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}", it "should delete a page" do
:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{@course.id}", :url=>@hidden_page.url) api_call(:delete, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
json.should == { "hide_from_students" => true, { :controller => 'wiki_pages_api', :action => 'destroy', :format => 'json', :course_id => @course.to_param,
"url" => @hidden_page.url, :url => @hidden_page.url })
"created_at" => @hidden_page.created_at.as_json, @hidden_page.reload.should be_deleted
"updated_at" => @hidden_page.updated_at.as_json, end
"title" => @hidden_page.title,
"body" => @hidden_page.body }
end
it "should update view count" do
views = @front_page.view_count
api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@front_page.url}",
:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{@course.id}", :url=>@front_page.url)
@front_page.reload
@front_page.view_count.should == views + 1
end
it "should return not-found on a nonexistent page" do
api_call(:get, "/api/v1/courses/#{@course.id}/pages/nonexistent",
{ :controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{@course.id}", :url=>'nonexistent' },
{}, {}, { :expected_status => 404 })
end end
context "unpublished pages" do context "unpublished pages" do
@ -89,12 +269,12 @@ describe "Pages API", :type => :integration do
it "should be in index" do it "should be in index" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages",
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}") :controller=>"wiki_pages_api", :action=>"index", :format=>"json", :course_id=>"#{@course.id}")
json.select{|w|w[:title] == @unpublished_page.title}.should_not be_nil json.select{|w|w[:title] == @unpublished_page.title}.should_not be_nil
end end
it "should show" do it "should show" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@unpublished_page.url}", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@unpublished_page.url}",
:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{@course.id}", :url=>@unpublished_page.url) :controller=>"wiki_pages_api", :action=>"show", :format=>"json", :course_id=>"#{@course.id}", :url=>@unpublished_page.url)
json['title'].should == @unpublished_page.title json['title'].should == @unpublished_page.title
end end
end end
@ -107,19 +287,20 @@ describe "Pages API", :type => :integration do
it "should list pages, excluding hidden ones" do it "should list pages, excluding hidden ones" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages",
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}") :controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{@course.id}")
json.should == [{"hide_from_students" => false, "url" => @front_page.url, "created_at" => @front_page.created_at.as_json, "updated_at" => @front_page.updated_at.as_json, "title" => @front_page.title}] json.map{|entry| entry.slice(*%w(hide_from_students url created_at updated_at title))}.should ==
[{"hide_from_students" => false, "url" => @front_page.url, "created_at" => @front_page.created_at.as_json, "updated_at" => @front_page.updated_at.as_json, "title" => @front_page.title}]
end end
it "should paginate, excluding hidden" do it "should paginate, excluding hidden" do
11.times { |i| @wiki.wiki_pages.create!(:title => "New Page #{i}") } 11.times { |i| @wiki.wiki_pages.create!(:title => "New Page #{i}") }
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages",
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}") :controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{@course.id}")
json.size.should == 10 json.size.should == 10
urls = json.collect{ |page| page['url'] } urls = json.collect{ |page| page['url'] }
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?page=2", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages?page=2",
:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}", :page => "2") :controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{@course.id}", :page => "2")
json.size.should == 2 json.size.should == 2
urls += json.collect{ |page| page['url'] } urls += json.collect{ |page| page['url'] }
@ -128,15 +309,15 @@ describe "Pages API", :type => :integration do
it "should refuse to show a hidden page" do it "should refuse to show a hidden page" do
api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}", api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@hidden_page.url}",
{:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{@course.id}", :url=>@hidden_page.url}, {:controller=>'wiki_pages_api', :action=>'show', :format=>'json', :course_id=>"#{@course.id}", :url=>@hidden_page.url},
{}, {}, { :expected_status => 401 }) {}, {}, { :expected_status => 401 })
end end
it "should refuse to list pages in an unpublished course" do it "should refuse to list pages in an unpublished course" do
@course.workflow_state = 'created' @course.workflow_state = 'created'
@course.save! @course.save!
api_call(:get, "/api/v1/courses/#{@course.id}/pages", api_call(:get, "/api/v1/courses/#{@course.id}/pages",
{:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}"}, {:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{@course.id}"},
{}, {}, { :expected_status => 401 }) {}, {}, { :expected_status => 401 })
end end
@ -149,11 +330,11 @@ describe "Pages API", :type => :integration do
other_page.save! other_page.save!
api_call(:get, "/api/v1/courses/#{other_course.id}/pages", api_call(:get, "/api/v1/courses/#{other_course.id}/pages",
{:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{other_course.id}"}, {:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{other_course.id}"},
{}, {}, { :expected_status => 401 }) {}, {}, { :expected_status => 401 })
api_call(:get, "/api/v1/courses/#{other_course.id}/pages/front-page", api_call(:get, "/api/v1/courses/#{other_course.id}/pages/front-page",
{:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{other_course.id}", :url=>'front-page'}, {:controller=>'wiki_pages_api', :action=>'show', :format=>'json', :course_id=>"#{other_course.id}", :url=>'front-page'},
{}, {}, { :expected_status => 401 }) {}, {}, { :expected_status => 401 })
end end
@ -166,11 +347,12 @@ describe "Pages API", :type => :integration do
other_page.workflow_state = 'active' other_page.workflow_state = 'active'
other_page.save! other_page.save!
api_call(:get, "/api/v1/courses/#{other_course.id}/pages", json = api_call(:get, "/api/v1/courses/#{other_course.id}/pages",
{:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{other_course.id}"}) {:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{other_course.id}"})
json.should_not be_empty
api_call(:get, "/api/v1/courses/#{other_course.id}/pages/front-page", api_call(:get, "/api/v1/courses/#{other_course.id}/pages/front-page",
{:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{other_course.id}", :url=>'front-page'}) {:controller=>'wiki_pages_api', :action=>'show', :format=>'json', :course_id=>"#{other_course.id}", :url=>'front-page'})
end end
it "should fulfill module progression requirements" do it "should fulfill module progression requirements" do
@ -181,13 +363,65 @@ describe "Pages API", :type => :integration do
# index should not affect anything # index should not affect anything
api_call(:get, "/api/v1/courses/#{@course.id}/pages", api_call(:get, "/api/v1/courses/#{@course.id}/pages",
{:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :course_id=>"#{@course.id}"}) {:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :course_id=>"#{@course.id}"})
mod.evaluate_for(@user).workflow_state.should == "unlocked" mod.evaluate_for(@user).workflow_state.should == "unlocked"
# show should count as a view # show should count as a view
api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@front_page.url}", api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@front_page.url}",
{:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :course_id=>"#{@course.id}", :url=>@front_page.url}) {:controller=>'wiki_pages_api', :action=>'show', :format=>'json', :course_id=>"#{@course.id}", :url=>@front_page.url})
mod.evaluate_for(@user).workflow_state.should == "completed" mod.evaluate_for(@user).workflow_state.should == "completed"
end
it "should not allow editing a page" do
api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@front_page.url}",
{ :controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @front_page.url },
{ :publish => false, :wiki_page => { :body => '!!!!' }}, {}, {:expected_status => 401})
@front_page.reload.body.should_not == '!!!!'
end
describe "with students in editing_roles" do
before do
@editable_page = @course.wiki.wiki_pages.create! :title => 'Editable Page', :editing_roles => 'students'
@editable_page.workflow_state = 'active'
@editable_page.save!
end
it "should allow editing the body, but not attributes" do
api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@editable_page.url}",
{ :controller => 'wiki_pages_api', :action => 'update', :format => 'json', :course_id => @course.to_param,
:url => @editable_page.url },
{ :wiki_page => { :published => false, :title => 'Broken Links', :body => '?!?!' }})
@editable_page.reload
@editable_page.should be_active
@editable_page.title.should == 'Editable Page'
@editable_page.body.should == '?!?!'
@editable_page.user_id.should == @student.id
end
it "should fulfill module completion requirements" do
mod = @course.context_modules.create!(:name => "some module")
tag = mod.add_item(:id => @editable_page.id, :type => 'wiki_page')
mod.completion_requirements = { tag.id => {:type => 'must_contribute'} }
mod.save!
api_call(:put, "/api/v1/courses/#{@course.id}/pages/#{@editable_page.url}",
{:controller=>'wiki_pages_api', :action=>'update', :format=>'json', :course_id=>"#{@course.id}",
:url=>@editable_page.url}, { :wiki_page => { :body => 'edited by student' }})
mod.evaluate_for(@user).workflow_state.should == "completed"
end
it "should not allow creating pages" do
api_call(:post, "/api/v1/courses/#{@course.id}/pages",
{ :controller => 'wiki_pages_api', :action => 'create', :format => 'json', :course_id => @course.to_param },
{}, {}, {:expected_status => 401})
end
it "should not allow deleting pages" do
api_call(:delete, "/api/v1/courses/#{@course.id}/pages/#{@editable_page.url}",
{ :controller => 'wiki_pages_api', :action => 'destroy', :format => 'json', :course_id => @course.to_param,
:url => @editable_page.url }, {}, {}, {:expected_status => 401})
end
end end
context "unpublished pages" do context "unpublished pages" do
@ -199,13 +433,13 @@ describe "Pages API", :type => :integration do
it "should not be in index" do it "should not be in index" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages",
:controller => "wiki_pages", :action => "api_index", :format => "json", :course_id => "#{@course.id}") :controller => "wiki_pages_api", :action => "index", :format => "json", :course_id => "#{@course.id}")
json.select { |w| w[:title] == @unpublished_page.title }.should == [] json.select { |w| w[:title] == @unpublished_page.title }.should == []
end end
it "should not show" do it "should not show" do
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@unpublished_page.url}", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@unpublished_page.url}",
{:controller => "wiki_pages", :action => "api_show", :format => "json", :course_id => "#{@course.id}", :url => @unpublished_page.url}, {:controller => "wiki_pages_api", :action => "show", :format => "json", :course_id => "#{@course.id}", :url => @unpublished_page.url},
{}, {}, {:expected_status => 401}) {}, {}, {:expected_status => 401})
end end
@ -213,7 +447,7 @@ describe "Pages API", :type => :integration do
@course.is_public = true @course.is_public = true
@course.save! @course.save!
json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@unpublished_page.url}", json = api_call(:get, "/api/v1/courses/#{@course.id}/pages/#{@unpublished_page.url}",
{:controller => "wiki_pages", :action => "api_show", :format => "json", :course_id => "#{@course.id}", :url => @unpublished_page.url}, {:controller => "wiki_pages_api", :action => "show", :format => "json", :course_id => "#{@course.id}", :url => @unpublished_page.url},
{}, {}, {:expected_status => 401}) {}, {}, {:expected_status => 401})
end end
end end
@ -227,15 +461,37 @@ describe "Pages API", :type => :integration do
it "should list the contents of a group wiki" do it "should list the contents of a group wiki" do
json = api_call(:get, "/api/v1/groups/#{@group.id}/pages", json = api_call(:get, "/api/v1/groups/#{@group.id}/pages",
{:controller=>"wiki_pages", :action=>"api_index", :format=>"json", :group_id=>"#{@group.id}"}) {:controller=>'wiki_pages_api', :action=>'index', :format=>'json', :group_id=>@group.to_param})
json.collect { |row| row['title'] }.should == @group.wiki.wiki_pages.active.order_by_id.collect(&:title) json.collect { |row| row['title'] }.should == @group.wiki.wiki_pages.active.order_by_id.collect(&:title)
end end
it "should retrieve page content from a group wiki" do it "should retrieve page content from a group wiki" do
testpage = @group.wiki.wiki_pages.last testpage = @group.wiki.wiki_pages.last
json = api_call(:get, "/api/v1/groups/#{@group.id}/pages/#{testpage.url}", json = api_call(:get, "/api/v1/groups/#{@group.id}/pages/#{testpage.url}",
{:controller=>"wiki_pages", :action=>"api_show", :format=>"json", :group_id=>"#{@group.id}", :url=>testpage.url}) {:controller=>'wiki_pages_api', :action=>'show', :format=>'json', :group_id=>@group.to_param, :url=>testpage.url})
json['body'].should == testpage.body json['body'].should == testpage.body
end end
it "should create a group wiki page" do
json = api_call(:post, "/api/v1/groups/#{@group.id}/pages?wiki_page[title]=newpage",
{:controller=>'wiki_pages_api', :action=>'create', :format=>'json', :group_id=>@group.to_param, :wiki_page => {'title' => 'newpage'}})
page = @group.wiki.wiki_pages.find_by_url!(json['url'])
page.title.should == 'newpage'
end
it "should update a group wiki page" do
testpage = @group.wiki.wiki_pages.first
api_call(:put, "/api/v1/groups/#{@group.id}/pages/#{testpage.url}?wiki_page[body]=lolcats",
{:controller=>'wiki_pages_api', :action=>'update', :format=>'json', :group_id=>@group.to_param, :url=>testpage.url, :wiki_page => {'body' => 'lolcats'}})
testpage.reload.body.should == 'lolcats'
end
it "should delete a group wiki page" do
count = @group.wiki.wiki_pages.not_deleted.size
testpage = @group.wiki.wiki_pages.last
api_call(:delete, "/api/v1/groups/#{@group.id}/pages/#{testpage.url}",
{:controller=>'wiki_pages_api', :action=>'destroy', :format=>'json', :group_id=>@group.to_param, :url=>testpage.url})
@group.reload.wiki.wiki_pages.not_deleted.size.should == count - 1
end
end end
end end