file upload api for courses, users, and groups
closes #7775 Allows specifying the folder to upload to as a slash-separated string, as well. test plan: upload to both the current user, and an allowed course, verify the workflow for s3 and local files. verify you can't upload to course you don't have permissions to, or another user. verify that you can specify a folder, and the folder will be created if it doesn't exist. Change-Id: Ib9082f047c1c93824fe65decf4789606d82450c6 Reviewed-on: https://gerrit.instructure.com/9603 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Cody Cutrer <cody@instructure.com>
This commit is contained in:
parent
64230d2746
commit
4da8dc2abf
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (C) 2011 Instructure, Inc.
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
|
@ -24,7 +24,7 @@ require 'set'
|
|||
class CoursesController < ApplicationController
|
||||
before_filter :require_user, :only => [:index]
|
||||
before_filter :require_pseudonym, :only => [:index]
|
||||
before_filter :require_context, :only => [:roster, :locks, :switch_role]
|
||||
before_filter :require_context, :only => [:roster, :locks, :switch_role, :create_file]
|
||||
|
||||
include Api::V1::Course
|
||||
|
||||
|
@ -152,6 +152,23 @@ class CoursesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# @API
|
||||
#
|
||||
# Upload a file to the course.
|
||||
#
|
||||
# This API endpoint is the first step in uploading a file to a course.
|
||||
# See the {file:file_uploads.html File Upload Documentation} for details on
|
||||
# the file upload workflow.
|
||||
#
|
||||
# Only those with the "Manage Files" permission on a course can upload files
|
||||
# to the course. By default, this is Teachers, TAs and Designers.
|
||||
def create_file
|
||||
@attachment = Attachment.new(:context => @context)
|
||||
if authorized_action(@attachment, @current_user, :create)
|
||||
api_attachment_preflight(@context, request)
|
||||
end
|
||||
end
|
||||
|
||||
def backup
|
||||
get_context
|
||||
if authorized_action(@context, @current_user, :update)
|
||||
|
|
|
@ -474,7 +474,7 @@ class FilesController < ApplicationController
|
|||
@attachment.uploaded_data = params[:file]
|
||||
if @attachment.save
|
||||
# for consistency with the s3 upload client flow, we redirect to the success url here to finish up
|
||||
redirect_to api_v1_files_create_success_url(@attachment, :uuid => @attachment.uuid)
|
||||
redirect_to api_v1_files_create_success_url(@attachment, :uuid => @attachment.uuid, :on_duplicate => params[:on_duplicate])
|
||||
else
|
||||
render(:nothing => true, :status => :bad_request)
|
||||
end
|
||||
|
@ -483,6 +483,8 @@ class FilesController < ApplicationController
|
|||
def api_create_success
|
||||
@attachment = Attachment.find_by_id_and_uuid(params[:id], params[:uuid])
|
||||
return render(:nothing => true, :status => :bad_request) unless @attachment.try(:file_state) == 'deleted'
|
||||
duplicate_handling = check_duplicate_handling_option(request)
|
||||
return unless duplicate_handling
|
||||
if Attachment.s3_storage?
|
||||
return render(:nothing => true, :status => :bad_request) unless @attachment.state == :unattached
|
||||
details = AWS::S3::S3Object.about(@attachment.full_filename, @attachment.bucket_name)
|
||||
|
@ -491,6 +493,7 @@ class FilesController < ApplicationController
|
|||
@attachment.file_state = 'available'
|
||||
@attachment.save!
|
||||
end
|
||||
@attachment.handle_duplicates(duplicate_handling)
|
||||
render :json => attachment_json(@attachment)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (C) 2011 Instructure, Inc.
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
|
@ -16,11 +16,16 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# @API Groups
|
||||
#
|
||||
# API for accessing group information.
|
||||
class GroupsController < ApplicationController
|
||||
before_filter :get_context
|
||||
before_filter :require_context, :only => [:create_category, :delete_category]
|
||||
before_filter :get_group_as_context, :only => [:show]
|
||||
|
||||
include Api::V1::Attachment
|
||||
|
||||
def context_group_members
|
||||
@group = @context
|
||||
if authorized_action(@group, @current_user, :read_roster)
|
||||
|
@ -287,6 +292,24 @@ class GroupsController < ApplicationController
|
|||
render :json => json
|
||||
end
|
||||
|
||||
# @API
|
||||
#
|
||||
# Upload a file to the group.
|
||||
#
|
||||
# This API endpoint is the first step in uploading a file to a group.
|
||||
# See the {file:file_uploads.html File Upload Documentation} for details on
|
||||
# the file upload workflow.
|
||||
#
|
||||
# Only those with the "Manage Files" permission on a group can upload files
|
||||
# to the group. By default, this is anybody participating in the
|
||||
# group, or any admin over the group.
|
||||
def create_file
|
||||
@attachment = Attachment.new(:context => @context)
|
||||
if authorized_action(@attachment, @current_user, :create)
|
||||
api_attachment_preflight(@context, request)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def get_group_as_context
|
||||
|
|
|
@ -412,6 +412,25 @@ class UsersController < ApplicationController
|
|||
render :json => { :hidden => true }
|
||||
end
|
||||
|
||||
# @API
|
||||
#
|
||||
# Upload a file to the user's personal files section.
|
||||
#
|
||||
# This API endpoint is the first step in uploading a file to a user's files.
|
||||
# See the {file:file_uploads.html File Upload Documentation} for details on
|
||||
# the file upload workflow.
|
||||
#
|
||||
# Note that typically users will only be able to upload files to their
|
||||
# own files section. Passing a user_id of +self+ is an easy shortcut
|
||||
# to specify the current user.
|
||||
def create_file
|
||||
@user = api_find(User, params[:user_id])
|
||||
@attachment = Attachment.new(:context => @user)
|
||||
if authorized_action(@attachment, @current_user, :create)
|
||||
api_attachment_preflight(@current_user, request)
|
||||
end
|
||||
end
|
||||
|
||||
def close_notification
|
||||
@current_user.close_announcement(AccountNotification.find(params[:id]))
|
||||
render :json => @current_user.to_json
|
||||
|
|
|
@ -649,6 +649,7 @@ ActionController::Routing::Routes.draw do |map|
|
|||
courses.delete 'courses/:id', :action => :destroy
|
||||
courses.post 'courses/:course_id/course_copy', :controller => :content_imports, :action => :copy_course_content
|
||||
courses.get 'courses/:course_id/course_copy/:id', :controller => :content_imports, :action => :copy_course_status, :path_name => :course_copy_status
|
||||
courses.post 'courses/:course_id/files', :action => :create_file
|
||||
end
|
||||
|
||||
api.with_options(:controller => :enrollments_api) do |enrollments|
|
||||
|
@ -735,6 +736,7 @@ ActionController::Routing::Routes.draw do |map|
|
|||
users.get 'accounts/:account_id/users', :action => :index, :path_name => 'account_users'
|
||||
|
||||
users.put 'users/:id', :action => :update
|
||||
users.post 'users/:user_id/files', :action => :create_file
|
||||
end
|
||||
|
||||
api.with_options(:controller => :pseudonyms) do |pseudonyms|
|
||||
|
@ -792,14 +794,18 @@ ActionController::Routing::Routes.draw do |map|
|
|||
events.post 'calendar_events/:id/reservations/:participant_id', :action => :reserve, :path_name => 'calendar_event_reserve'
|
||||
end
|
||||
|
||||
api.with_options(:controller => :appointment_groups) do |groups|
|
||||
groups.get 'appointment_groups', :action => :index, :path_name => 'appointment_groups'
|
||||
groups.post 'appointment_groups', :action => :create
|
||||
groups.get 'appointment_groups/:id', :action => :show, :path_name => 'appointment_group'
|
||||
groups.put 'appointment_groups/:id', :action => :update
|
||||
groups.delete 'appointment_groups/:id', :action => :destroy
|
||||
groups.get 'appointment_groups/:id/users', :action => :users, :path_name => 'appointment_group_users'
|
||||
groups.get 'appointment_groups/:id/groups', :action => :groups, :path_name => 'appointment_group_groups'
|
||||
api.with_options(:controller => :appointment_groups) do |appt_groups|
|
||||
appt_groups.get 'appointment_groups', :action => :index, :path_name => 'appointment_groups'
|
||||
appt_groups.post 'appointment_groups', :action => :create
|
||||
appt_groups.get 'appointment_groups/:id', :action => :show, :path_name => 'appointment_group'
|
||||
appt_groups.put 'appointment_groups/:id', :action => :update
|
||||
appt_groups.delete 'appointment_groups/:id', :action => :destroy
|
||||
appt_groups.get 'appointment_groups/:id/users', :action => :users, :path_name => 'appointment_group_users'
|
||||
appt_groups.get 'appointment_groups/:id/groups', :action => :groups, :path_name => 'appointment_group_groups'
|
||||
end
|
||||
|
||||
api.with_options(:controller => :groups) do |groups|
|
||||
groups.post 'groups/:group_id/files', :action => :create_file
|
||||
end
|
||||
|
||||
api.post 'files/:id/create_success', :controller => :files, :action => :api_create_success, :path_name => 'files_create_success'
|
||||
|
|
|
@ -22,14 +22,17 @@ Arguments:
|
|||
<dt>name</dt> <dd>The filename of the file. Any UTF-8 name is allowed. Path components such as `/` and `\` will be treated as part of the filename, not a path to a sub-folder.</dd>
|
||||
<dt>size</dt> <dd>The size of the file, in bytes.</dd>
|
||||
<dt>content_type</dt> <dd>The content type of the file. If not given, it will be guessed based on the file extension.</dd>
|
||||
<dt>folder</dt> <dd>The path of the folder to store the file in. The path separator is the forward slash `/`, never a back slash. The folder will be created if it does not already exist. This parameter only applies to file uploads in a context that has folders, such as a user, a course, or a group. If not given, a default folder will be used.</dd>
|
||||
<dt>on_duplicate</dt> <dd>How to handle duplicate filenames. If `overwrite`, then this file upload will overwrite any other file in the folder with the same name. If `rename`, then this file will be renamed if another file in the folder exists with the given name. If no parameter is given, the default is `overwrite`. This doesn't apply to file uploads in a context that doesn't have folders.</dd>
|
||||
</dl>
|
||||
|
||||
Example Request:
|
||||
|
||||
curl 'https://<canvas>/api/v1/users/self/files' \
|
||||
-F 'name=profile_pic.jpg' \
|
||||
-F 'size=302185' \
|
||||
-F 'content_type=image/jpeg' \
|
||||
curl 'https://<canvas>/api/v1/users/self/files' \
|
||||
-F 'name=profile_pic.jpg' \
|
||||
-F 'size=302185' \
|
||||
-F 'content_type=image/jpeg' \
|
||||
-F 'folder=my_files/section1' \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
Example Response:
|
||||
|
|
|
@ -41,10 +41,26 @@ module Api::V1::Attachment
|
|||
@attachment.file_state = 'deleted'
|
||||
@attachment.workflow_state = 'unattached'
|
||||
@attachment.content_type = request.params[:content_type].presence || Attachment.mimetype(@attachment.filename)
|
||||
if opts.key?(:folder)
|
||||
@attachment.folder = folder
|
||||
elsif context.respond_to?(:folders) && request.params[:folder].is_a?(String)
|
||||
@attachment.folder = Folder.assert_path(request.params[:folder], context)
|
||||
end
|
||||
duplicate_handling = check_duplicate_handling_option(request)
|
||||
duplicate_handling = nil if duplicate_handling == 'overwrite'
|
||||
@attachment.save!
|
||||
render :json => @attachment.ajax_upload_params(@current_pseudonym,
|
||||
api_v1_files_create_url,
|
||||
api_v1_files_create_success_url(@attachment, :uuid => @attachment.uuid),
|
||||
api_v1_files_create_url(:on_duplicate => duplicate_handling),
|
||||
api_v1_files_create_success_url(@attachment, :uuid => @attachment.uuid, :on_duplicate => duplicate_handling),
|
||||
:ssl => request.ssl?).slice(:upload_url, :upload_params)
|
||||
end
|
||||
|
||||
def check_duplicate_handling_option(request)
|
||||
duplicate_handling = request.params[:on_duplicate].presence || 'overwrite'
|
||||
unless %w(rename overwrite).include?(duplicate_handling)
|
||||
render(:json => { :message => 'invalid on_duplicate option' }, :status => :bad_request)
|
||||
return nil
|
||||
end
|
||||
duplicate_handling
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
#
|
||||
# Copyright (C) 2012 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__) + '/api_spec_helper')
|
||||
|
||||
shared_examples_for "file uploads api" do
|
||||
it "should upload (local files)" do
|
||||
filename = "my_essay.doc"
|
||||
content = "this is a test doc"
|
||||
|
||||
local_storage!
|
||||
# step 1, preflight
|
||||
json = preflight({ :name => filename })
|
||||
json['upload_url'].should == "http://www.example.com/files_api"
|
||||
|
||||
# step 2, upload
|
||||
tmpfile = Tempfile.new(["test", File.extname(filename)])
|
||||
tmpfile.write(content)
|
||||
tmpfile.rewind
|
||||
post_params = json["upload_params"].merge({"file" => tmpfile})
|
||||
send_multipart(json["upload_url"], post_params)
|
||||
|
||||
attachment = Attachment.last(:order => :id)
|
||||
attachment.should be_deleted
|
||||
response.should redirect_to("http://www.example.com/api/v1/files/#{attachment.id}/create_success?uuid=#{attachment.uuid}")
|
||||
|
||||
# step 3, confirmation
|
||||
post response['Location'], {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
attachment.reload
|
||||
json = json_parse(response.body)
|
||||
json.should == {
|
||||
'id' => attachment.id,
|
||||
'url' => file_download_url(attachment, :verifier => attachment.uuid, :download => '1', :download_frd => '1'),
|
||||
'content-type' => attachment.content_type,
|
||||
'display_name' => attachment.display_name,
|
||||
'filename' => attachment.filename,
|
||||
'size' => tmpfile.size,
|
||||
}
|
||||
|
||||
attachment.file_state.should == 'available'
|
||||
attachment.content_type.should == "application/msword"
|
||||
attachment.open.read.should == content
|
||||
attachment.display_name.should == filename
|
||||
attachment
|
||||
end
|
||||
|
||||
it "should upload (s3 files)" do
|
||||
filename = "my_essay.doc"
|
||||
content = "this is a test doc"
|
||||
|
||||
s3_storage!
|
||||
# step 1, preflight
|
||||
json = preflight({ :name => filename })
|
||||
json['upload_url'].should == "http://no-bucket.s3.amazonaws.com/"
|
||||
attachment = Attachment.last(:order => :id)
|
||||
redir = json['upload_params']['success_action_redirect']
|
||||
redir.should == "http://www.example.com/api/v1/files/#{attachment.id}/create_success?uuid=#{attachment.uuid}"
|
||||
attachment.should be_deleted
|
||||
|
||||
# step 2, upload
|
||||
# we skip the actual call and stub this out, since we can't hit s3 during specs
|
||||
AWS::S3::S3Object.expects(:about).with(attachment.full_filename, attachment.bucket_name).returns({
|
||||
'content-type' => 'application/msword',
|
||||
'content-length' => 1234,
|
||||
})
|
||||
|
||||
# step 3, confirmation
|
||||
post redir, {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
attachment.reload
|
||||
json = json_parse(response.body)
|
||||
json.should == {
|
||||
'id' => attachment.id,
|
||||
'url' => file_download_url(attachment, :verifier => attachment.uuid, :download => '1', :download_frd => '1'),
|
||||
'content-type' => attachment.content_type,
|
||||
'display_name' => attachment.display_name,
|
||||
'filename' => attachment.filename,
|
||||
'size' => 1234,
|
||||
}
|
||||
|
||||
attachment.file_state.should == 'available'
|
||||
attachment.content_type.should == "application/msword"
|
||||
attachment.display_name.should == filename
|
||||
attachment
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "file uploads api with folders" do
|
||||
it_should_behave_like "file uploads api"
|
||||
|
||||
it "should allow specifying a folder" do
|
||||
preflight({ :name => "with_path.txt", :folder => "files/a/b/c/mypath" })
|
||||
attachment = Attachment.last(:order => :id)
|
||||
attachment.folder.should == Folder.assert_path("/files/a/b/c/mypath", context)
|
||||
end
|
||||
|
||||
it "should upload to an existing folder" do
|
||||
@folder = Folder.assert_path("/files/a/b/c/mypath", context)
|
||||
@folder.should be_present
|
||||
@folder.should be_visible
|
||||
preflight({ :name => "my_essay.doc", :folder => "files/a/b/c/mypath" })
|
||||
attachment = Attachment.last(:order => :id)
|
||||
attachment.folder.should == @folder
|
||||
end
|
||||
|
||||
it "should overwrite duplicate files by default" do
|
||||
local_storage!
|
||||
@folder = Folder.assert_path("test", context)
|
||||
a1 = Attachment.create!(:folder => @folder, :context => context, :filename => "test.txt", :uploaded_data => StringIO.new("first"))
|
||||
json = preflight({ :name => "test.txt", :folder => "test" })
|
||||
|
||||
tmpfile = Tempfile.new(["test", ".txt"])
|
||||
tmpfile.write("second")
|
||||
tmpfile.rewind
|
||||
post_params = json["upload_params"].merge({"file" => tmpfile})
|
||||
send_multipart(json["upload_url"], post_params)
|
||||
post response['Location'], {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
attachment = Attachment.last(:order => :id)
|
||||
a1.reload.should be_deleted
|
||||
attachment.reload.should be_available
|
||||
end
|
||||
|
||||
it "should allow renaming instead of overwriting duplicate files (local storage)" do
|
||||
local_storage!
|
||||
@folder = Folder.assert_path("test", context)
|
||||
a1 = Attachment.create!(:folder => @folder, :context => context, :filename => "test.txt", :uploaded_data => StringIO.new("first"))
|
||||
json = preflight({ :name => "test.txt", :folder => "test", :on_duplicate => 'rename' })
|
||||
|
||||
tmpfile = Tempfile.new(["test", ".txt"])
|
||||
tmpfile.write("second")
|
||||
tmpfile.rewind
|
||||
post_params = json["upload_params"].merge({"file" => tmpfile})
|
||||
send_multipart(json["upload_url"], post_params)
|
||||
post response['Location'], {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
attachment = Attachment.last(:order => :id)
|
||||
a1.reload.should be_available
|
||||
attachment.reload.should be_available
|
||||
attachment.display_name.should == "test-1.txt"
|
||||
end
|
||||
|
||||
it "should allow renaming instead of overwriting duplicate files (s3 storage)" do
|
||||
s3_storage!
|
||||
@folder = Folder.assert_path("test", context)
|
||||
a1 = Attachment.create!(:folder => @folder, :context => context, :filename => "test.txt", :uploaded_data => StringIO.new("first"))
|
||||
json = preflight({ :name => "test.txt", :folder => "test", :on_duplicate => 'rename' })
|
||||
|
||||
redir = json['upload_params']['success_action_redirect']
|
||||
attachment = Attachment.last(:order => :id)
|
||||
AWS::S3::S3Object.expects(:about).with(attachment.full_filename, attachment.bucket_name).returns({
|
||||
'content-type' => 'application/msword',
|
||||
'content-length' => 1234,
|
||||
})
|
||||
|
||||
post redir, {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
a1.reload.should be_available
|
||||
attachment.reload.should be_available
|
||||
attachment.display_name.should == "test-1.txt"
|
||||
end
|
||||
|
||||
it "should reject other duplicate file handling params" do
|
||||
proc { preflight({ :name => "test.txt", :folder => "test", :on_duplicate => 'killall' }) }.should raise_error
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (C) 2011 Instructure, Inc.
|
||||
# Copyright (C) 2012 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../api_spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../file_uploads_spec_helper')
|
||||
|
||||
class TestCourseApi
|
||||
include Api::V1::Course
|
||||
|
@ -499,6 +500,28 @@ describe CoursesController, :type => :integration do
|
|||
{ :controller => 'courses', :action => 'show', :id => @course1.to_param, :format => 'json' })
|
||||
json['id'].should == @course1.id
|
||||
end
|
||||
|
||||
context "course files" do
|
||||
it_should_behave_like "file uploads api with folders"
|
||||
|
||||
def preflight(preflight_params)
|
||||
@user = @teacher
|
||||
api_call(:post, "/api/v1/courses/#{@course.id}/files",
|
||||
{ :controller => "courses", :action => "create_file", :format => "json", :course_id => @course.to_param, },
|
||||
preflight_params)
|
||||
end
|
||||
|
||||
def context
|
||||
@course
|
||||
end
|
||||
|
||||
it "should require the correct permission to upload" do
|
||||
@user = student_in_course(:course => @course).user
|
||||
api_call(:post, "/api/v1/courses/#{@course.id}/files",
|
||||
{ :controller => "courses", :action => "create_file", :format => "json", :course_id => @course.to_param, },
|
||||
{ :name => 'failboat.txt' }, {}, :expected_status => 401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def each_copy_option
|
||||
|
@ -676,5 +699,4 @@ describe ContentImportsController, :type => :integration do
|
|||
check_counts(1, option)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Copyright (C) 2012 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__) + '/../api_spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../file_uploads_spec_helper')
|
||||
|
||||
describe "Groups API", :type => :integration do
|
||||
context "group files" do
|
||||
it_should_behave_like "file uploads api with folders"
|
||||
|
||||
before do
|
||||
group_model
|
||||
@group.add_user(user_with_pseudonym)
|
||||
end
|
||||
|
||||
def preflight(preflight_params)
|
||||
api_call(:post, "/api/v1/groups/#{@group.id}/files",
|
||||
{ :controller => "groups", :action => "create_file", :format => "json", :group_id => @group.to_param, },
|
||||
preflight_params)
|
||||
end
|
||||
|
||||
def context
|
||||
@group
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../api_spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../file_uploads_spec_helper')
|
||||
|
||||
describe 'Submissions API', :type => :integration do
|
||||
|
||||
|
@ -1583,83 +1584,12 @@ describe 'Submissions API', :type => :integration do
|
|||
@user = @student1
|
||||
end
|
||||
|
||||
it "should allow uploading files to the student's own submission (local files)" do
|
||||
local_storage!
|
||||
it_should_behave_like "file uploads api"
|
||||
|
||||
# step 1, preflight
|
||||
json = api_call(:post, "/api/v1/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@student1.id}/files",
|
||||
def preflight(preflight_params)
|
||||
api_call(:post, "/api/v1/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@student1.id}/files",
|
||||
{ :controller => "submissions_api", :action => "create_file", :format => "json", :course_id => @course.to_param, :assignment_id => @assignment.to_param, :user_id => @student1.to_param },
|
||||
{ :name => "my_essay.doc" })
|
||||
json['upload_url'].should == "http://www.example.com/files_api"
|
||||
|
||||
# step 2, upload
|
||||
tmpfile = Tempfile.new(["test", ".doc"])
|
||||
tmpfile.write("this is a test doc")
|
||||
tmpfile.rewind
|
||||
post_params = json["upload_params"].merge({"file" => tmpfile})
|
||||
send_multipart(json["upload_url"], post_params)
|
||||
|
||||
attachment = Attachment.last(:order => :id)
|
||||
response.should redirect_to("http://www.example.com/api/v1/files/#{attachment.id}/create_success?uuid=#{attachment.uuid}")
|
||||
|
||||
# step 3, confirmation
|
||||
post response['Location'], {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
attachment.reload
|
||||
json = json_parse(response.body)
|
||||
json.should == {
|
||||
'id' => attachment.id,
|
||||
'url' => file_download_url(attachment, :verifier => attachment.uuid, :download => '1', :download_frd => '1'),
|
||||
'content-type' => attachment.content_type,
|
||||
'display_name' => attachment.display_name,
|
||||
'filename' => attachment.filename,
|
||||
'size' => tmpfile.size,
|
||||
}
|
||||
|
||||
attachment.context.should == @student1
|
||||
attachment.file_state.should == 'available'
|
||||
attachment.content_type.should == "application/msword"
|
||||
attachment.open.read.should == "this is a test doc"
|
||||
attachment.display_name.should == "my_essay.doc"
|
||||
end
|
||||
|
||||
it "should allow uploading files to the student's own submission (s3 files)" do
|
||||
s3_storage!
|
||||
|
||||
# step 1, preflight
|
||||
json = api_call(:post, "/api/v1/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@student1.id}/files",
|
||||
{ :controller => "submissions_api", :action => "create_file", :format => "json", :course_id => @course.to_param, :assignment_id => @assignment.to_param, :user_id => @student1.to_param },
|
||||
{ :name => "my_essay.doc" })
|
||||
json['upload_url'].should == "http://no-bucket.s3.amazonaws.com/"
|
||||
attachment = Attachment.last(:order => :id)
|
||||
redir = json['upload_params']['success_action_redirect']
|
||||
redir.should == "http://www.example.com/api/v1/files/#{attachment.id}/create_success?uuid=#{attachment.uuid}"
|
||||
|
||||
# step 2, upload
|
||||
# we skip the actual call and stub this out, since we can't hit s3 during specs
|
||||
AWS::S3::S3Object.expects(:about).with(attachment.full_filename, attachment.bucket_name).returns({
|
||||
'content-type' => 'application/msword',
|
||||
'content-length' => 1234,
|
||||
})
|
||||
|
||||
# step 3, confirmation
|
||||
post redir, {}, { 'Authorization' => "Bearer #{@user.access_tokens.first.token}" }
|
||||
response.should be_success
|
||||
attachment.reload
|
||||
json = json_parse(response.body)
|
||||
json.should == {
|
||||
'id' => attachment.id,
|
||||
'url' => file_download_url(attachment, :verifier => attachment.uuid, :download => '1', :download_frd => '1'),
|
||||
'content-type' => attachment.content_type,
|
||||
'display_name' => attachment.display_name,
|
||||
'filename' => attachment.filename,
|
||||
'size' => 1234,
|
||||
}
|
||||
|
||||
attachment.context.should == @student1
|
||||
attachment.file_state.should == 'available'
|
||||
attachment.content_type.should == "application/msword"
|
||||
attachment.display_name.should == "my_essay.doc"
|
||||
preflight_params)
|
||||
end
|
||||
|
||||
it "should reject uploading files to other students' submissions" do
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../api_spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../file_uploads_spec_helper')
|
||||
|
||||
class TestUserApi
|
||||
include Api::V1::User
|
||||
|
@ -473,4 +474,25 @@ describe "Users API", :type => :integration do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "user files" do
|
||||
it_should_behave_like "file uploads api with folders"
|
||||
|
||||
def preflight(preflight_params)
|
||||
api_call(:post, "/api/v1/users/self/files",
|
||||
{ :controller => "users", :action => "create_file", :format => "json", :user_id => 'self', },
|
||||
preflight_params)
|
||||
end
|
||||
|
||||
def context
|
||||
@user
|
||||
end
|
||||
|
||||
it "should not allow uploading to other users" do
|
||||
user2 = User.create!
|
||||
api_call(:post, "/api/v1/users/#{user2.id}/files",
|
||||
{ :controller => "users", :action => "create_file", :format => "json", :user_id => user2.to_param, },
|
||||
{ :name => "my_essay.doc" }, {}, :expected_status => 401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue