diff --git a/lib/travis/api/app.rb b/lib/travis/api/app.rb index e5e74bbd..8ab3759b 100644 --- a/lib/travis/api/app.rb +++ b/lib/travis/api/app.rb @@ -16,84 +16,93 @@ require 'newrelic_rpm' # run Travis::Api::App.new # # Requires TLS in production. -class Travis::Api::App - autoload :AccessToken, 'travis/api/app/access_token' - autoload :Base, 'travis/api/app/base' - autoload :Endpoint, 'travis/api/app/endpoint' - autoload :Extensions, 'travis/api/app/extensions' - autoload :Helpers, 'travis/api/app/helpers' - autoload :Middleware, 'travis/api/app/middleware' - autoload :Responders, 'travis/api/app/responders' +module Travis::Api + ACCEPT_VERSION = /vnd\.travis-ci\.(\d+)\+/ + DEFAULT_VERSION = 'v2' - Rack.autoload :SSL, 'rack/ssl' - - # Used to track if setup already ran. - def self.setup? - @setup ||= false + def version(string) + string =~ ACCEPT_VERSION && "v#{$1}" || DEFAULT_VERSION end - # Loads all endpoints and middleware and hooks them up properly. - # Calls #setup on any middleware and endpoint. - # - # This method is not threadsafe, but called when loading - # the environment, so no biggy. - def self.setup(options = {}) - setup! unless setup? - Endpoint.set(options) - end + class App + autoload :AccessToken, 'travis/api/app/access_token' + autoload :Base, 'travis/api/app/base' + autoload :Endpoint, 'travis/api/app/endpoint' + autoload :Extensions, 'travis/api/app/extensions' + autoload :Helpers, 'travis/api/app/helpers' + autoload :Middleware, 'travis/api/app/middleware' + autoload :Responders, 'travis/api/app/responders' - def self.new(options = {}) - setup(options) if options - super() - end + Rack.autoload :SSL, 'rack/ssl' - attr_accessor :app + # Used to track if setup already ran. + def self.setup? + @setup ||= false + end - def initialize - @app = Rack::Builder.app do - use Hubble::Rescuer, env: Travis.env, codename: ENV['CODENAME'] if Endpoint.production? && ENV['HUBBLE_ENDPOINT'] - use Rack::Protection::PathTraversal - use Rack::SSL if Endpoint.production? - use Rack::Deflater - use Rack::PostBodyContentTypeParser - use Rack::JSONP - use ActiveRecord::ConnectionAdapters::ConnectionManagement + # Loads all endpoints and middleware and hooks them up properly. + # Calls #setup on any middleware and endpoint. + # + # This method is not threadsafe, but called when loading + # the environment, so no biggy. + def self.setup(options = {}) + setup! unless setup? + Endpoint.set(options) + end - use Rack::Config do |env| - env['travis.global_prefix'] = env['SCRIPT_NAME'] + def self.new(options = {}) + setup(options) if options + super() + end + + attr_accessor :app + + def initialize + @app = Rack::Builder.app do + use Hubble::Rescuer, env: Travis.env, codename: ENV['CODENAME'] if Endpoint.production? && ENV['HUBBLE_ENDPOINT'] + use Rack::Protection::PathTraversal + use Rack::SSL if Endpoint.production? + use Rack::Deflater + use Rack::PostBodyContentTypeParser + use Rack::JSONP + use ActiveRecord::ConnectionAdapters::ConnectionManagement + + use Rack::Config do |env| + env['travis.global_prefix'] = env['SCRIPT_NAME'] + end + + Middleware.subclasses.each { |m| use(m) } + Endpoint.subclasses.each { |e| map(e.prefix) { run(e.new) } } + end + end + + # Rack protocol + def call(env) + app.call(env) + end + + private + + def self.setup! + setup_travis + load_endpoints + setup_endpoints + @setup = true end - Middleware.subclasses.each { |m| use(m) } - Endpoint.subclasses.each { |e| map(e.prefix) { run(e.new) } } - end + def self.setup_travis + Travis::Amqp.config = Travis.config.amqp + Travis::Database.connect + Travis.services = Travis::Services + end + + def self.load_endpoints + Backports.require_relative_dir 'app/middleware' + Backports.require_relative_dir 'app/endpoint' + end + + def self.setup_endpoints + Base.subclasses.each(&:setup) + end end - - # Rack protocol - def call(env) - app.call(env) - end - - private - - def self.setup! - setup_travis - load_endpoints - setup_endpoints - @setup = true - end - - def self.setup_travis - Travis::Amqp.config = Travis.config.amqp - Travis::Database.connect - Travis.services = Travis::Services - end - - def self.load_endpoints - Backports.require_relative_dir 'app/middleware' - Backports.require_relative_dir 'app/endpoint' - end - - def self.setup_endpoints - Base.subclasses.each(&:setup) - end end diff --git a/lib/travis/api/app/middleware/rewrite.rb b/lib/travis/api/app/middleware/rewrite.rb new file mode 100644 index 00000000..c23fe7bf --- /dev/null +++ b/lib/travis/api/app/middleware/rewrite.rb @@ -0,0 +1,30 @@ +require 'travis/api/app' + +class Travis::Api::App + class Middleware + class Rewrite < Middleware + V1_REPO_URL = %r(^(/[^/]+/[^/]+(?:/builds(?:/[\d]+)?|/cc\.xml)?)$) + + set(:setup) { ActiveRecord::Base.logger = Travis.logger } + + after do +p not_found? + force_redirect("/repositories#{$1}") if response.status == 404 && version == 'v1' && request.path =~ V1_REPO_URL + end + + private + + def force_redirect(path) + response.body = '' + response['Content-Length'] = '0' + response['Content-Type'] = '' + redirect(path) + end + + def version + API.version(request.accept.join) + end + end + end +end + diff --git a/lib/travis/api/app/responders/json.rb b/lib/travis/api/app/responders/json.rb index ff32852e..6e5b7e60 100644 --- a/lib/travis/api/app/responders/json.rb +++ b/lib/travis/api/app/responders/json.rb @@ -1,8 +1,5 @@ module Travis::Api::App::Responders class Json < Base - ACCEPT_VERSION = /vnd\.travis-ci\.(\d+)\+/ - DEFAULT_VERSION = 'v2' - def apply? options[:format] == 'json' && !resource.is_a?(String) end @@ -22,7 +19,7 @@ module Travis::Api::App::Responders end def version - request.accept.join =~ ACCEPT_VERSION && "v#{$1}" || DEFAULT_VERSION + API.version(request.accept.join) end end end diff --git a/spec/integration/v1/repositories_spec.rb b/spec/integration/v1/repositories_spec.rb index 22d39518..a62b8fe2 100644 --- a/spec/integration/v1/repositories_spec.rb +++ b/spec/integration/v1/repositories_spec.rb @@ -31,14 +31,14 @@ describe 'Repos' do response.should deliver_json_for(Repository.by_slug('svenfuchs/minimal').first, version: 'v1') end - xit 'GET /svenfuchs/minimal' do + it 'GET /svenfuchs/minimal' do response = get '/svenfuchs/minimal', {}, headers - response.should deliver_json_for(Repository.by_slug('svenfuchs/minimal').first, version: 'v1') + response.should redirect_to('/repositories/svenfuchs/minimal') end - xit 'GET /svenfuchs/minimal/cc.xml' do # TODO wat. + it 'GET /svenfuchs/minimal/cc.xml' do response = get '/svenfuchs/minimal/cc.xml' - response.should deliver_xml_for() + response.should redirect_to('/repositories/svenfuchs/minimal/cc.xml') end describe 'GET /svenfuchs/minimal.png' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f4977e8a..ce872233 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,44 +10,7 @@ require 'multi_json' require 'travis/api/app' require 'travis/testing/scenario' require 'travis/testing/factories' - -RSpec::Matchers.define :deliver_json_for do |resource, options = {}| - match do |response| - actual = parse(response.body) - expected = Travis::Api.data(resource, options) - - failure_message_for_should do - "expected\n\n#{actual}\n\nto equal\n\n#{expected}" - end - - actual == expected - end - - def parse(body) - MultiJson.decode(body) - end -end - -RSpec::Matchers.define :deliver_result_image_for do |name| - match do |response| - actual = files.detect do |(name, content)| - response.body.force_encoding('ascii') == content.force_encoding('ascii') # TODO ummmmmmmm? - end - actual = actual && actual[0] - - failure_message_for_should do - "expected #{actual.inspect} to equal #{name.inspect}" - end - - actual == name - end - - def files - files = Hash[*Dir['public/images/result/*.png'].map do |file| - [File.basename(file, '.png'), File.read(file)] - end.flatten] - end -end +require 'support/matchers' Travis.logger = Logger.new(StringIO.new) Travis::Api::App.setup diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb new file mode 100644 index 00000000..bbfb3ba3 --- /dev/null +++ b/spec/support/matchers.rb @@ -0,0 +1,62 @@ +# TODO move to travis-core? + +RSpec::Matchers.define :deliver_json_for do |resource, options = {}| + match do |response| + if response.status == 200 + actual = parse(response.body) + expected = Travis::Api.data(resource, options) + + failure_message_for_should do + "expected\n\n#{actual}\n\nto equal\n\n#{expected}" + end + + actual == expected + else + failure_message_for_should do + "expected the request to be successful (200) but was #{response.status}" + end + false + end + end + + def parse(body) + MultiJson.decode(body) + end +end + +RSpec::Matchers.define :deliver_result_image_for do |name| + match do |response| + actual = files.detect do |(name, content)| + response.body.force_encoding('ascii') == content.force_encoding('ascii') # TODO ummmmmmmm? + end + actual = actual && actual[0] + + failure_message_for_should do + "expected #{actual.inspect} to equal #{name.inspect}" + end + + actual == name + end + + def files + files = Hash[*Dir['public/images/result/*.png'].map do |file| + [File.basename(file, '.png'), File.read(file)] + end.flatten] + end +end + +RSpec::Matchers.define :redirect_to do |expected| + match do |response| + actual = response.headers['location'].to_s.sub('http://example.org', '') + + failure_message_for_should do + "expected to be redirect to #{expected} but was not. status: #{response.status}, location: #{actual}" + end + + failure_message_for_should_not do + "expected not to be redirect to #{expected} but was." + end + + actual == expected + end +end