Extract canvas_http into seperate gem.

Change-Id: Ib90c12b99d30853a0a1a0235c9aa1b5dd645f614
fixes: CNVS-11949
Reviewed-on: https://gerrit.instructure.com/32176
Tested-by: Jenkins <jenkins@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
Reviewed-by: Stanley Stuart <stanley@instructure.com>
QA-Review: August Thornton <august@instructure.com>
This commit is contained in:
Joseph Rodriguez 2014-03-20 09:52:26 -06:00 committed by Simon Williams
parent b9d3460aaa
commit ccd8b38d6a
21 changed files with 319 additions and 264 deletions

View File

@ -118,6 +118,7 @@ gem 'adheres_to_policy', :path => 'gems/adheres_to_policy'
gem 'canvas_breach_mitigation', :path => 'gems/canvas_breach_mitigation'
gem 'canvas_color', :path => 'gems/canvas_color'
gem 'canvas_crummy', :path => 'gems/canvas_crummy'
gem 'canvas_http', :path => 'gems/canvas_http'
gem 'canvas_mimetype_fu', :path => 'gems/canvas_mimetype_fu'
gem 'canvas_sanitize', :path => 'gems/canvas_sanitize'
gem 'canvas_statsd', :path => 'gems/canvas_statsd'

View File

@ -763,7 +763,7 @@ class AccountsController < ApplicationController
def validated_turnitin_host(input_host)
if input_host.present?
_, turnitin_uri = CustomValidations.validate_url(input_host)
_, turnitin_uri = CanvasHttp.validate_url(input_host)
turnitin_uri.host
else
nil

View File

@ -61,7 +61,7 @@ class ExternalContentController < ApplicationController
endpoint = params[:endpoint]
url = params[:url]
uri = URI.parse(endpoint + (endpoint.match(/\?/) ? '&url=' : '?url=') + CGI.escape(url) + '&format=json')
res = Canvas::HTTP.get(uri.to_s) rescue '{}'
res = CanvasHttp.get(uri.to_s) rescue '{}'
data = JSON.parse(res.body) rescue {}
if data['type']
if data['type'] == 'photo' && data['url'].try(:match, /^http/)

View File

@ -846,7 +846,7 @@ class Account < ActiveRecord::Base
return if self.settings[:auth_discovery_url].blank?
begin
value, uri = CustomValidations.validate_url(self.settings[:auth_discovery_url])
value, uri = CanvasHttp.validate_url(self.settings[:auth_discovery_url])
self.auth_discovery_url = value
rescue URI::InvalidURIError, ArgumentError
errors.add(:discovery_url, t('errors.invalid_discovery_url', "The discovery URL is not valid" ))

View File

@ -1749,7 +1749,7 @@ class Attachment < ActiveRecord::Base
def clone_url(url, duplicate_handling, check_quota, opts={})
begin
Canvas::HTTP.clone_url_as_attachment(url, :attachment => self)
Attachment.clone_url_as_attachment(url, :attachment => self)
if check_quota
self.save! # save to calculate attachment size, otherwise self.size is nil
@ -1765,11 +1765,11 @@ class Attachment < ActiveRecord::Base
self.file_state = 'errored'
self.workflow_state = 'errored'
case e
when Canvas::HTTP::TooManyRedirectsError
when CanvasHttp::TooManyRedirectsError
self.upload_error_message = t :upload_error_too_many_redirects, "Too many redirects"
when Canvas::HTTP::InvalidResponseCodeError
when CanvasHttp::InvalidResponseCodeError
self.upload_error_message = t :upload_error_invalid_response_code, "Invalid response code, expected 200 got %{code}", :code => e.code
when CustomValidations::RelativeUriError
when CanvasHttp::RelativeUriError
self.upload_error_message = t :upload_error_relative_uri, "No host provided for the URL: %{url}", :url => url
when URI::InvalidURIError, ArgumentError
# assigning all ArgumentError to InvalidUri may be incorrect
@ -1825,4 +1825,38 @@ class Attachment < ActiveRecord::Base
def can_unpublish?
false
end
# Download a URL using a GET request and return a new un-saved Attachment
# with the data at that URL. Tries to detect the correct content_type as
# well.
#
# This handles large files well.
#
# Pass an existing attachment in opts[:attachment] to use that, rather than
# creating a new attachment.
def self.clone_url_as_attachment(url, opts = {})
_, uri = CanvasHttp.validate_url(url)
CanvasHttp.get(url) do |http_response|
if http_response.code.to_i == 200
tmpfile = CanvasHttp.tempfile_for_uri(uri)
# net/http doesn't make this very obvious, but read_body can take any
# object that responds to << as the destination of the body, and it'll
# stream in chunks rather than reading the whole body into memory (as
# long as you use the block form of http.request, which
# CanvasHttp.get does)
http_response.read_body(tmpfile)
tmpfile.rewind
attachment = opts[:attachment] || Attachment.new(:filename => File.basename(uri.path))
attachment.filename ||= File.basename(uri.path)
attachment.uploaded_data = tmpfile
if attachment.content_type.blank? || attachment.content_type == "unknown/unknown"
attachment.content_type = http_response.content_type
end
return attachment
else
raise CanvasHttp::InvalidResponseCodeError.new(http_response.code.to_i)
end
end
end
end

View File

@ -99,7 +99,7 @@ class ContextExternalTool < ActiveRecord::Base
def validate_vendor_help_link
return if self.vendor_help_link.blank?
begin
value, uri = CustomValidations.validate_url(self.vendor_help_link)
value, uri = CanvasHttp.validate_url(self.vendor_help_link)
self.vendor_help_link = uri.to_s
rescue URI::InvalidURIError, ArgumentError
self.vendor_help_link = nil

4
gems/canvas_http/Gemfile Normal file
View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in canvas_http.gemspec
gemspec

View File

@ -0,0 +1 @@
require "bundler/gem_tasks"

View File

@ -0,0 +1,21 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Gem::Specification.new do |spec|
spec.name = "canvas_http"
spec.version = "1.0.0"
spec.authors = ["Brian Palmer"]
spec.email = ["brianp@instructure.com"]
spec.summary = %q{Canvas HTTP}
spec.files = Dir.glob("{lib,spec}/**/*") + %w(Rakefile test.sh)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.5"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"
spec.add_development_dependency "webmock"
end

View File

@ -0,0 +1,82 @@
require 'uri'
module CanvasHttp
class Error < ::Exception; end
class TooManyRedirectsError < CanvasHttp::Error; end
class InvalidResponseCodeError < CanvasHttp::Error
attr_reader :code
def initialize(code)
super()
@code = code
end
end
class RelativeUriError < ArgumentError; end
# Use this helper method to do HTTP GET requests. It knows how to handle
# HTTPS urls, and follows redirects to a given depth.
#
# Returns the Net::HTTPResponse object, not just the raw response body. If a
# block is passed in, the response will also be yielded to the block without
# the body having been read yet -- this allows for streaming the response
# rather than reading it all into memory.
#
# Eventually it may be expanded to optionally do cert verification as well.
#
# TODO: this doesn't yet handle relative redirects (relative Location HTTP
# header), which actually isn't even technically allowed by the HTTP spec.
# But everybody allows and handles it.
def self.get(url_str, other_headers = {}, redirect_limit = 3)
loop do
raise(TooManyRedirectsError) if redirect_limit <= 0
_, uri = CanvasHttp.validate_url(url_str)
request = Net::HTTP::Get.new(uri.request_uri, other_headers)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.request(request) do |response|
case response
when Net::HTTPRedirection
url_str = response['Location']
redirect_limit -= 1
else
if block_given?
yield response
else
response.body
end
return response
end
end
end
end
# returns [normalized_url_string, URI] if valid, raises otherwise
def self.validate_url(value)
value = value.strip
raise ArgumentError if value.empty?
uri = URI.parse(value)
unless uri.scheme
value = "http://#{value}"
uri = URI.parse(value)
end
raise ArgumentError unless %w(http https).include?(uri.scheme.downcase)
raise(RelativeUriError) if uri.host.nil? || uri.host.strip.empty?
return value, uri
end
# returns a tempfile with a filename based on the uri (same extension, if
# there was an extension)
def self.tempfile_for_uri(uri)
basename = File.basename(uri.path)
basename, ext = basename.split(".", 2)
tmpfile = if ext
Tempfile.new([basename, ext])
else
Tempfile.new(basename)
end
tmpfile.set_encoding(Encoding::BINARY) if tmpfile.respond_to?(:set_encoding)
tmpfile.binmode
tmpfile
end
end

View File

@ -0,0 +1,77 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'spec_helper'
require 'webmock'
describe "CanvasHttp" do
include WebMock::API
describe ".get" do
it "should return response objects" do
stub_request(:get, "http://www.example.com/a/b").
to_return(body: "Hello", headers: { 'Content-Length' => 5 })
res = CanvasHttp.get("http://www.example.com/a/b")
res.should be_a Net::HTTPOK
res.body.should == "Hello"
end
it "does not use ssl" do
http = double.as_null_object
Net::HTTP.stub(:new) { http }
expect(http).to receive(:use_ssl=).with(false)
response = double('Response')
expect(response).to receive(:body)
expect(http).to receive(:request).and_yield(response)
CanvasHttp.get("http://www.example.com/a/b")
end
it "should use ssl" do
http = double
Net::HTTP.stub(:new) { http }
expect(http).to receive(:use_ssl=).with(true)
expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
expect(http).to receive(:request).and_yield(double(body: 'Hello SSL'))
CanvasHttp.get("https://www.example.com/a/b").body.should == "Hello SSL"
end
it "should follow redirects" do
stub_request(:get, "http://www.example.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example2.com/a'})
stub_request(:get, "http://www.example2.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example3.com/a'})
stub_request(:get, "http://www.example3.com/a").
to_return(body: "Hello", headers: { 'Content-Length' => 5 })
res = CanvasHttp.get("http://www.example.com/a")
res.should be_a Net::HTTPOK
res.body.should == "Hello"
end
it "should fail on too many redirects" do
stub_request(:get, "http://www.example.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example2.com/a'})
stub_request(:get, "http://www.example2.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example3.com/a'})
expect { CanvasHttp.get("http://www.example.com/a", {}, 2) }.to raise_error(CanvasHttp::TooManyRedirectsError)
end
end
end

View File

@ -0,0 +1,10 @@
require 'canvas_http'
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
config.filter_run :focus
config.color_enabled true
config.order = 'random'
end

14
gems/canvas_http/test.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
result=0
bundle install
bundle exec rspec spec
let result=$result+$?
if [ $result -eq 0 ]; then
echo "SUCCESS"
else
echo "FAILURE"
fi
exit $result

View File

@ -30,7 +30,7 @@ module AppCenter
response = Rails.cache.fetch(cache_key, :expires_in => expires) do
uri = URI.parse("#{base_url}#{endpoint}")
uri.query = [uri.query, "offset=#{offset}"].compact.join('&')
Canvas::HTTP.get(uri.to_s).body
CanvasHttp.get(uri.to_s).body
end
json = JSON.parse(response)
@ -128,7 +128,7 @@ module AppCenter
uri.query = URI.encode_www_form(params)
uri.to_s
response = Canvas::HTTP.get(uri.to_s).body
response = CanvasHttp.get(uri.to_s).body
json = JSON.parse(response)
json = json['reviews'].first if json['reviews']
rescue

View File

@ -1,101 +0,0 @@
require 'uri'
module Canvas::HTTP
class Error < ::Exception; end
class TooManyRedirectsError < Canvas::HTTP::Error; end
class InvalidResponseCodeError < Canvas::HTTP::Error
attr_reader :code
def initialize(code)
super()
@code = code
end
end
# Use this helper method to do HTTP GET requests. It knows how to handle
# HTTPS urls, and follows redirects to a given depth.
#
# Returns the Net::HTTPResponse object, not just the raw response body. If a
# block is passed in, the response will also be yielded to the block without
# the body having been read yet -- this allows for streaming the response
# rather than reading it all into memory.
#
# Eventually it may be expanded to optionally do cert verification as well.
#
# TODO: this doesn't yet handle relative redirects (relative Location HTTP
# header), which actually isn't even technically allowed by the HTTP spec.
# But everybody allows and handles it.
def self.get(url_str, other_headers = {}, redirect_limit = 3)
loop do
raise(TooManyRedirectsError) if redirect_limit <= 0
url, uri = CustomValidations.validate_url(url_str)
request = Net::HTTP::Get.new(uri.request_uri, other_headers)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.request(request) do |response|
case response
when Net::HTTPRedirection
url_str = response['Location']
redirect_limit -= 1
else
if block_given?
yield response
else
response.body
end
return response
end
end
end
end
# Download a URL using a GET request and return a new un-saved Attachment
# with the data at that URL. Tries to detect the correct content_type as
# well.
#
# This handles large files well.
#
# Pass an existing attachment in opts[:attachment] to use that, rather than
# creating a new attachment.
def self.clone_url_as_attachment(url, opts = {})
_, uri = CustomValidations.validate_url(url)
Canvas::HTTP.get(url) do |http_response|
if http_response.code.to_i == 200
tmpfile = tempfile_for_uri(uri)
# net/http doesn't make this very obvious, but read_body can take any
# object that responds to << as the destination of the body, and it'll
# stream in chunks rather than reading the whole body into memory (as
# long as you use the block form of http.request, which
# Canvas::HTTP.get does)
http_response.read_body(tmpfile)
tmpfile.rewind
attachment = opts[:attachment] || Attachment.new(:filename => File.basename(uri.path))
attachment.filename ||= File.basename(uri.path)
attachment.uploaded_data = tmpfile
if attachment.content_type.blank? || attachment.content_type == "unknown/unknown"
attachment.content_type = http_response.content_type
end
return attachment
else
raise InvalidResponseCodeError.new(http_response.code.to_i)
end
end
end
# returns a tempfile with a filename based on the uri (same extension, if
# there was an extension)
def self.tempfile_for_uri(uri)
basename = File.basename(uri.path)
basename, ext = basename.split(".", 2)
tmpfile = if ext
Tempfile.new([basename, ext])
else
Tempfile.new(basename)
end
tmpfile.set_encoding(Encoding::BINARY) if tmpfile.respond_to?(:set_encoding)
tmpfile.binmode
tmpfile
end
end

View File

@ -17,28 +17,15 @@
#
module CustomValidations
class RelativeUriError < ArgumentError; end
# returns [normalized_url_string, URI] if valid, raises otherwise
def self.validate_url(value)
value = value.strip
raise ArgumentError if value.empty?
uri = URI.parse(value)
unless uri.scheme
value = "http://#{value}"
uri = URI.parse(value)
end
raise(RelativeUriError) if uri.host.blank?
raise ArgumentError unless %w(http https).include?(uri.scheme.downcase)
return value, uri
end
module ClassMethods
def validates_as_url(*fields)
validates_each(fields, :allow_nil => true) do |record, attr, value|
begin
value, uri = CustomValidations.validate_url(value)
value, uri = CanvasHttp.validate_url(value)
record.send("#{attr}=", value)
rescue URI::InvalidURIError, ArgumentError
record.errors.add attr, 'is not a valid URL'

View File

@ -244,7 +244,7 @@ module Kaltura
data = {}
data[:result] = result
url = result.css('logFileUrl')[0].content
csv = CSV.parse(Canvas::HTTP.get(url).body)
csv = CSV.parse(CanvasHttp.get(url).body)
data[:entries] = []
csv.each do |row|
data[:entries] << {

View File

@ -141,7 +141,7 @@ shared_examples_for "file uploads api" do
json = api_call(:get, status_url, {:id => attachment.id.to_s, :controller => 'files', :action => 'api_file_status', :format => 'json', :uuid => attachment.uuid})
json['upload_status'].should == 'pending'
Canvas::HTTP.expects(:get).with("http://www.example.com/images/delete.png").yields(FakeHttpResponse.new(200, "asdf"))
CanvasHttp.expects(:get).with("http://www.example.com/images/delete.png").yields(FakeHttpResponse.new(200, "asdf"))
run_download_job
json = api_call(:get, status_url, {:id => attachment.id.to_s, :controller => 'files', :action => 'api_file_status', :format => 'json', :uuid => attachment.uuid})
@ -194,7 +194,7 @@ shared_examples_for "file uploads api" do
local_storage!
# step 1, preflight
Canvas::HTTP.expects(:get).with(url).yields(FakeHttpResponse.new(404))
CanvasHttp.expects(:get).with(url).yields(FakeHttpResponse.new(404))
json = preflight({ :name => filename, :size => 20, :url => url })
attachment = Attachment.order(:id).last
json['status_url'].should == "http://www.example.com/api/v1/files/#{attachment.id}/#{attachment.uuid}/status"
@ -213,7 +213,7 @@ shared_examples_for "file uploads api" do
local_storage!
# step 1, preflight
Canvas::HTTP.expects(:get).with(url).raises(Timeout::Error)
CanvasHttp.expects(:get).with(url).raises(Timeout::Error)
json = preflight({ :name => filename, :size => 20, :url => url })
attachment = Attachment.order(:id).last
json['status_url'].should == "http://www.example.com/api/v1/files/#{attachment.id}/#{attachment.uuid}/status"
@ -232,7 +232,7 @@ shared_examples_for "file uploads api" do
local_storage!
# step 1, preflight
Canvas::HTTP.expects(:get).with(url).raises(Canvas::HTTP::TooManyRedirectsError)
CanvasHttp.expects(:get).with(url).raises(CanvasHttp::TooManyRedirectsError)
json = preflight({ :name => filename, :size => 20, :url => url })
attachment = Attachment.order(:id).last
attachment.workflow_state.should == 'unattached'
@ -312,7 +312,7 @@ shared_examples_for "file uploads api with folders" do
a1 = Attachment.create!(:folder => @folder, :context => context, :filename => "test.txt", :uploaded_data => StringIO.new("first"))
json = preflight({ :name => "test.txt", :folder => "test", :url => "http://www.example.com/test" })
attachment = Attachment.order(:id).last
Canvas::HTTP.expects(:get).with("http://www.example.com/test").yields(FakeHttpResponse.new(200, "second"))
CanvasHttp.expects(:get).with("http://www.example.com/test").yields(FakeHttpResponse.new(200, "second"))
run_jobs
a1.reload.should be_deleted
@ -349,7 +349,7 @@ shared_examples_for "file uploads api with folders" do
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', :url => "http://www.example.com/test" })
attachment = Attachment.order(:id).last
Canvas::HTTP.expects(:get).with("http://www.example.com/test").yields(FakeHttpResponse.new(200, "second"))
CanvasHttp.expects(:get).with("http://www.example.com/test").yields(FakeHttpResponse.new(200, "second"))
run_jobs
a1.reload.should be_available
@ -461,7 +461,7 @@ shared_examples_for "file uploads api with quotas" do
json = preflight({ :name => "test.txt", :url => "http://www.example.com/test" })
status_url = json['status_url']
attachment = Attachment.order(:id).last
Canvas::HTTP.expects(:get).with("http://www.example.com/test").yields(FakeHttpResponse.new(200, (" " * 2.megabytes)))
CanvasHttp.expects(:get).with("http://www.example.com/test").yields(FakeHttpResponse.new(200, (" " * 2.megabytes)))
run_jobs
json = api_call(:get, status_url, {:id => attachment.id.to_s, :controller => 'files', :action => 'api_file_status', :format => 'json', :uuid => attachment.uuid})

View File

@ -56,20 +56,20 @@ describe AppCenter::AppApi do
endpoint = '/?myparam=value'
per_page = 11
page = 3
Canvas::HTTP.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{page * per_page - per_page}").returns(response)
CanvasHttp.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{page * per_page - per_page}").returns(response)
api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
end
it "can handle an invalid response" do
response.stubs(:body).returns('')
Canvas::HTTP.expects(:get).returns(response)
CanvasHttp.expects(:get).returns(response)
api.fetch_app_center_response('', 12.minutes, 8, 3).should == {}
end
it "can handle an error response" do
message = {"message" => "Tool not found", "type" => "error"}
response.stubs(:body).returns(message.to_json)
Canvas::HTTP.expects(:get).returns(response)
CanvasHttp.expects(:get).returns(response)
api.fetch_app_center_response('', 13.minutes, 6, 9).should == message
end
@ -78,7 +78,7 @@ describe AppCenter::AppApi do
per_page = 1
page = 1
offset = page * per_page - per_page
Canvas::HTTP.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{offset}").returns(response)
CanvasHttp.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{offset}").returns(response)
response = api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
results = response['objects']
results.size.should == 1
@ -93,7 +93,7 @@ describe AppCenter::AppApi do
per_page = 5
page = 1
offset = page * per_page - per_page
Canvas::HTTP.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{offset}").returns(response)
CanvasHttp.expects(:get).with("#{api.app_center.settings['base_url']}#{endpoint}&offset=#{offset}").returns(response)
response = api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
results = response['objects']
results.size.should == 4
@ -104,7 +104,7 @@ describe AppCenter::AppApi do
it "resets the cache when getting an invalid response" do
enable_cache do
response.stubs(:body).returns('')
Canvas::HTTP.expects(:get).returns(response).twice()
CanvasHttp.expects(:get).returns(response).twice()
api.fetch_app_center_response('', 13.minutes, 7, 4).should == {}
api.fetch_app_center_response('', 13.minutes, 7, 4).should == {}
end
@ -112,7 +112,7 @@ describe AppCenter::AppApi do
it "uses the configured token as part of the cache key" do
enable_cache do
Canvas::HTTP.expects(:get).returns(response).twice()
CanvasHttp.expects(:get).returns(response).twice()
api.fetch_app_center_response('/endpoint/url', 13.minutes, 7, 4)
@ -152,14 +152,14 @@ describe AppCenter::AppApi do
end
it "gets a list of apps" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
apps = api.get_apps()['lti_apps']
apps.should be_a Array
apps.size.should == 2
end
it "returns an empty hash if the app center is disabled" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
setting = PluginSetting.find_by_name(api.app_center.id)
setting.destroy
@ -172,7 +172,7 @@ describe AppCenter::AppApi do
it "gets the next page" do
enable_cache do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
response = api.get_apps
response['meta']['next_page'].should == 2
end
@ -180,7 +180,7 @@ describe AppCenter::AppApi do
it "caches apps results" do
enable_cache do
Canvas::HTTP.expects(:get).returns(response).once
CanvasHttp.expects(:get).returns(response).once
api.get_apps()
api.get_apps()
end
@ -188,7 +188,7 @@ describe AppCenter::AppApi do
it "caches multiple calls" do
enable_cache do
Canvas::HTTP.expects(:get).returns(response).times(2)
CanvasHttp.expects(:get).returns(response).times(2)
api.get_apps(0)
api.get_apps(1)
api.get_apps(0)
@ -261,7 +261,7 @@ describe AppCenter::AppApi do
}
response.stubs(:body).returns({"objects" => [app]}.to_json)
Canvas::HTTP.expects(:get).returns(response)
CanvasHttp.expects(:get).returns(response)
json = api.get_apps(0)
tool = json['lti_apps'].first
tool['short_name'].should == app['id']
@ -344,7 +344,7 @@ describe AppCenter::AppApi do
]
}
response.stubs(:body).returns({"lti_apps" => [app]}.to_json)
Canvas::HTTP.expects(:get).returns(response)
CanvasHttp.expects(:get).returns(response)
json = api.get_apps(0)
tool = json['lti_apps'].first
@ -375,7 +375,7 @@ describe AppCenter::AppApi do
end
it "gets an apps user review" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
review = api.get_app_user_review('first_tool', 12345)
review.should be_a Hash
review['rating'].should == 3
@ -383,7 +383,7 @@ describe AppCenter::AppApi do
end
it "returns an empty hash if the app center is disabled" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
setting = PluginSetting.find_by_name(api.app_center.id)
setting.destroy
@ -509,14 +509,14 @@ describe AppCenter::AppApi do
end
it "gets an apps reviews" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
reviews = api.get_app_reviews('first_tool')['reviews']
reviews.should be_a Array
reviews.size.should == 2
end
it "returns an empty hash if the app center is disabled" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
setting = PluginSetting.find_by_name(api.app_center.id)
setting.destroy
@ -528,14 +528,14 @@ describe AppCenter::AppApi do
end
it "gets the next page" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
response = api.get_app_reviews('first_tool')
response['meta']['next_page'].should == 2
end
it "caches apps results" do
enable_cache do
Canvas::HTTP.expects(:get).returns(response)
CanvasHttp.expects(:get).returns(response)
api.get_app_reviews('first_tool')
api.get_app_reviews('first_tool')
end
@ -543,7 +543,7 @@ describe AppCenter::AppApi do
it "caches multiple calls" do
enable_cache do
Canvas::HTTP.expects(:get).returns(response).times(2)
CanvasHttp.expects(:get).returns(response).times(2)
api.get_app_reviews('first_tool', 0)
api.get_app_reviews('first_tool', 1)
api.get_app_reviews('first_tool', 0)
@ -552,7 +552,7 @@ describe AppCenter::AppApi do
end
it "can handle an edu-apps api v1 response" do
Canvas::HTTP.stubs(:get).returns(response)
CanvasHttp.stubs(:get).returns(response)
reviews = api.get_app_reviews('first_tool')['reviews']
reviews.first.should be_key('user')
reviews.first['user'].should be_key('name')

View File

@ -1,108 +0,0 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
require_webmock
describe "Canvas::HTTP" do
include WebMock::API
before do
WebMock.enable!
end
after do
WebMock.reset!
WebMock.disable!
end
describe ".get" do
it "should return response objects" do
http_stub = Net::HTTP.any_instance
http_stub.expects(:use_ssl=).never
stub_request(:get, "http://www.example.com/a/b").
to_return(body: "Hello", headers: { 'Content-Length' => 5 })
res = Canvas::HTTP.get("http://www.example.com/a/b")
res.should be_a Net::HTTPOK
res.body.should == "Hello"
end
it "should use ssl" do
http_stub = Net::HTTP.any_instance
res = mock('response', :body => 'test')
http_stub.expects(:use_ssl=).with(true)
http_stub.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
http_stub.expects(:request).yields(res)
Canvas::HTTP.get("https://www.example.com/a/b").should == res
end
it "should follow redirects" do
stub_request(:get, "http://www.example.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example2.com/a'})
stub_request(:get, "http://www.example2.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example3.com/a'})
stub_request(:get, "http://www.example3.com/a").
to_return(body: "Hello", headers: { 'Content-Length' => 5 })
res = Canvas::HTTP.get("http://www.example.com/a")
res.should be_a Net::HTTPOK
res.body.should == "Hello"
end
it "should fail on too many redirects" do
stub_request(:get, "http://www.example.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example2.com/a'})
stub_request(:get, "http://www.example2.com/a").
to_return(status: 301, headers: { 'Location' => 'http://www.example3.com/a'})
expect { Canvas::HTTP.get("http://www.example.com/a", {}, 2) }.to raise_error(Canvas::HTTP::TooManyRedirectsError)
end
end
describe ".clone_url_as_attachment" do
it "should reject invalid urls" do
expect { Canvas::HTTP.clone_url_as_attachment("ftp://some/stuff") }.to raise_error(ArgumentError)
end
it "should not raise on non-200 responses" do
url = "http://example.com/test.png"
Canvas::HTTP.expects(:get).with(url).yields(stub('code' => '401'))
expect { Canvas::HTTP.clone_url_as_attachment(url) }.to raise_error(Canvas::HTTP::InvalidResponseCodeError)
end
it "should use an existing attachment if passed in" do
url = "http://example.com/test.png"
a = attachment_model
Canvas::HTTP.expects(:get).with(url).yields(FakeHttpResponse.new('200', 'this is a jpeg', 'content-type' => 'image/jpeg'))
Canvas::HTTP.clone_url_as_attachment(url, :attachment => a)
a.save!
a.open.read.should == "this is a jpeg"
end
it "should detect the content_type from the body" do
url = "http://example.com/test.png"
Canvas::HTTP.expects(:get).with(url).yields(FakeHttpResponse.new('200', 'this is a jpeg', 'content-type' => 'image/jpeg'))
att = Canvas::HTTP.clone_url_as_attachment(url)
att.should be_present
att.should be_new_record
att.content_type.should == 'image/jpeg'
att.context = Account.default
att.save!
att.open.read.should == 'this is a jpeg'
end
end
end

View File

@ -1641,6 +1641,39 @@ describe Attachment do
end
end
describe ".clone_url_as_attachment" do
it "should reject invalid urls" do
expect { Attachment.clone_url_as_attachment("ftp://some/stuff") }.to raise_error(ArgumentError)
end
it "should not raise on non-200 responses" do
url = "http://example.com/test.png"
CanvasHttp.expects(:get).with(url).yields(stub('code' => '401'))
expect { Attachment.clone_url_as_attachment(url) }.to raise_error(CanvasHttp::InvalidResponseCodeError)
end
it "should use an existing attachment if passed in" do
url = "http://example.com/test.png"
a = attachment_model
CanvasHttp.expects(:get).with(url).yields(FakeHttpResponse.new('200', 'this is a jpeg', 'content-type' => 'image/jpeg'))
Attachment.clone_url_as_attachment(url, :attachment => a)
a.save!
a.open.read.should == "this is a jpeg"
end
it "should detect the content_type from the body" do
url = "http://example.com/test.png"
CanvasHttp.expects(:get).with(url).yields(FakeHttpResponse.new('200', 'this is a jpeg', 'content-type' => 'image/jpeg'))
att = Attachment.clone_url_as_attachment(url)
att.should be_present
att.should be_new_record
att.content_type.should == 'image/jpeg'
att.context = Account.default
att.save!
att.open.read.should == 'this is a jpeg'
end
end
end
def processing_model