Browse Source
for a given cask: - checks required fields - checks URL responds successfully - checks content_length specified
phinze
12 years ago
5 changed files with 231 additions and 0 deletions
@ -0,0 +1,128 @@ |
|||
class Cask::Audit |
|||
attr_reader :cask, :errors, :warnings, :headers, :response_status |
|||
|
|||
def initialize(cask) |
|||
@cask = cask |
|||
@errors = [] |
|||
@warnings = [] |
|||
@headers = {} |
|||
end |
|||
|
|||
def run! |
|||
_check_required_fields |
|||
return if errors? |
|||
_get_data_from_request |
|||
return if errors? |
|||
_check_response_status |
|||
return if errors? |
|||
_check_content_length |
|||
end |
|||
|
|||
def add_error(message) |
|||
@errors << message |
|||
end |
|||
|
|||
def add_warning(message) |
|||
@warnings << message |
|||
end |
|||
|
|||
def errors? |
|||
!@errors.empty? |
|||
end |
|||
|
|||
def warnings? |
|||
!@warnings.empty? |
|||
end |
|||
|
|||
def result |
|||
if errors? |
|||
"#{Tty.red}failed#{Tty.reset}" |
|||
elsif warnings? |
|||
"#{Tty.yellow}warning#{Tty.reset}" |
|||
else |
|||
"#{Tty.green}passed#{Tty.reset}" |
|||
end |
|||
end |
|||
|
|||
def summary |
|||
summary = ["audit for #{cask}: #{result}"] |
|||
|
|||
@errors.each do |error| |
|||
summary << " #{Tty.red}-#{Tty.reset} #{error}" |
|||
end |
|||
|
|||
@warnings.each do |warning| |
|||
summary << " #{Tty.yellow}-#{Tty.reset} #{warning}" |
|||
end |
|||
|
|||
summary.join("\n") |
|||
end |
|||
|
|||
def _check_required_fields |
|||
add_error "url is required" unless cask.url |
|||
add_error "version is required" unless cask.version |
|||
add_error "homepage is required" unless cask.homepage |
|||
end |
|||
|
|||
http_responses = [ |
|||
'HTTP/1.0 200 OK', |
|||
'HTTP/1.1 200 OK' |
|||
] |
|||
|
|||
OK_RESPONSES = { |
|||
'http' => http_responses, |
|||
'https' => http_responses, |
|||
'ftp' => [ 'OK' ] |
|||
} |
|||
|
|||
def _check_response_status |
|||
ok = OK_RESPONSES[cask.url.scheme] |
|||
unless ok.include?(@response_status) |
|||
add_error "unexpected http response, expecting #{ok.map(&:inspect).join(' or ')}, got #{@response_status.inspect}" |
|||
end |
|||
end |
|||
|
|||
def _check_content_length |
|||
remote_content_length = @headers['Content-Length'] |
|||
if cask.content_length.nil? |
|||
add_warning "specify content_length so we can check against URL, currently: content_length '#{remote_content_length}'" |
|||
else |
|||
unless cask.content_length == remote_content_length |
|||
add_warning "unexpected content_length for #{cask}; specified #{cask.content_length.inspect}, but got #{remote_content_length.inspect}" |
|||
end |
|||
end |
|||
end |
|||
|
|||
def _get_data_from_request |
|||
response = _curl(cask.url) |
|||
|
|||
if response.empty? |
|||
add_error "timeout while requesting #{cask.url}" |
|||
return |
|||
end |
|||
|
|||
response_lines = response.split("\n").map(&:chomp) |
|||
|
|||
case cask.url.scheme |
|||
when 'http', 'https' then |
|||
@response_status = response_lines.grep(/^HTTP/).last |
|||
http_headers = response_lines[(response_lines.index(@response_status)+1)..-1] |
|||
http_headers.each { |line| |
|||
header_name, header_value = line.split(': ') |
|||
@headers[header_name] = header_value |
|||
} |
|||
when 'ftp' then |
|||
@response_status = 'OK' |
|||
response_lines.each { |line| |
|||
header_name, header_value = line.split(': ') |
|||
@headers[header_name] = header_value |
|||
} |
|||
else |
|||
add_error "unknown scheme for #{cask.url}" |
|||
end |
|||
end |
|||
|
|||
def _curl(url) |
|||
`curl --max-time 5 --silent --location --head '#{url}'` |
|||
end |
|||
end |
@ -0,0 +1,7 @@ |
|||
class Cask::Auditor |
|||
def self.audit(cask) |
|||
audit = Cask::Audit.new(cask) |
|||
audit.run! |
|||
puts audit.summary |
|||
end |
|||
end |
@ -0,0 +1,12 @@ |
|||
class Cask::CLI::Audit |
|||
def self.run(*args) |
|||
casks_to_audit = args.empty? ? Cask.all : args.map { |arg| Cask.load(arg) } |
|||
casks_to_audit.each do |cask| |
|||
Cask::Auditor.audit(cask) |
|||
end |
|||
end |
|||
|
|||
def self.help |
|||
"verifies installability of casks" |
|||
end |
|||
end |
@ -0,0 +1,78 @@ |
|||
require 'test_helper' |
|||
|
|||
describe Cask::Audit do |
|||
describe "result" do |
|||
it "is 'failed' if there are have been any errors added" do |
|||
audit = Cask::Audit.new(mock()) |
|||
audit.add_error 'bad' |
|||
audit.add_warning 'eh' |
|||
audit.result.must_match /failed/ |
|||
end |
|||
|
|||
it "is 'warning' if there are no errors, but there are warnings" do |
|||
audit = Cask::Audit.new(mock()) |
|||
audit.add_warning 'eh' |
|||
audit.result.must_match /warning/ |
|||
end |
|||
|
|||
it "is 'passed' if there are no errors or warning" do |
|||
audit = Cask::Audit.new(mock()) |
|||
audit.result.must_match /passed/ |
|||
end |
|||
end |
|||
|
|||
describe "run!" do |
|||
describe "required fields" do |
|||
it "adds an error if url is missing" do |
|||
audit = Cask::Audit.new(stub(:url => nil, :version => 'something', :homepage => 'something')) |
|||
audit.run! |
|||
audit.errors.must_include 'url is required' |
|||
end |
|||
|
|||
it "adds an error if version is missing" do |
|||
audit = Cask::Audit.new(stub(:url => 'something', :version => nil, :homepage => 'something')) |
|||
audit.run! |
|||
audit.errors.must_include 'version is required' |
|||
end |
|||
|
|||
it "adds an error if homepage is missing" do |
|||
audit = Cask::Audit.new(stub(:url => 'something', :version => 'something', :homepage => nil)) |
|||
audit.run! |
|||
audit.errors.must_include 'homepage is required' |
|||
end |
|||
end |
|||
|
|||
describe "request processing" do |
|||
it "adds an error if response is empty" do |
|||
audit = Cask::Audit.new(stub(:url => 'something', :version => 'something', :homepage => 'something')) |
|||
audit.stubs(:_curl).returns('') |
|||
audit.run! |
|||
audit.errors.must_include 'timeout while requesting something' |
|||
end |
|||
|
|||
it "properly populates the response code and headers from an http response" do |
|||
audit = Cask::Audit.new(stub( |
|||
:url => URI('http://something/file.zip'), |
|||
:version => 'something', |
|||
:homepage => 'something', |
|||
:content_length => '123' |
|||
)) |
|||
audit.stubs(:_curl).returns(<<-RESPONSE.gsub(/^ /, '')) |
|||
HTTP/1.1 200 OK |
|||
Content-Type: application/x-apple-diskimage |
|||
ETag: "b4208f3e84967be4b078ecaa03fba941" |
|||
Content-Length: 23726161 |
|||
Last-Modified: Sun, 12 Aug 2012 21:17:21 GMT |
|||
RESPONSE |
|||
audit.run! |
|||
audit.response_status.must_equal 'HTTP/1.1 200 OK' |
|||
audit.headers.must_equal({ |
|||
'Content-Type' => 'application/x-apple-diskimage', |
|||
'ETag' => '"b4208f3e84967be4b078ecaa03fba941"', |
|||
'Content-Length' => '23726161', |
|||
'Last-Modified' => 'Sun, 12 Aug 2012 21:17:21 GMT' |
|||
}) |
|||
end |
|||
end |
|||
end |
|||
end |
Loading…
Reference in new issue