Skip to content

Commit

Permalink
rewrite hub fork tests as cukes running against a live server
Browse files Browse the repository at this point in the history
  • Loading branch information
mislav committed May 6, 2012
1 parent c72c647 commit d1e03eb
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 74 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -3,5 +3,7 @@ source 'https://rubygems.org'
gem 'ronn'
gem 'aruba'
gem 'cucumber'
gem 'sinatra'
gem 'thin'

gemspec
108 changes: 108 additions & 0 deletions features/fork.feature
@@ -0,0 +1,108 @@
Feature: hub fork
Background:
Given I am in "dotfiles" git repo
And the "origin" remote has url "git://github.com/evilchelu/dotfiles.git"
And I am "mislav" on github.com with OAuth token "OTOKEN"

Scenario: Fork the repository
Given the GitHub API server:
"""
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' }
get('/repos/evilchelu/dotfiles', :host_name => 'api.github.com') { '' }
post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') { '' }
"""
When I successfully run `hub fork`
Then the output should contain exactly "new remote: mislav\n"
And "git remote add -f mislav git@github.com:mislav/dotfiles.git" should be run
And the url for "mislav" should be "git@github.com:mislav/dotfiles.git"

Scenario: --no-remote
Given the GitHub API server:
"""
get('/repos/evilchelu/dotfiles') { '' }
post('/repos/evilchelu/dotfiles/forks') { '' }
"""
When I successfully run `hub fork --no-remote`
Then there should be no output
And there should be no "mislav" remote

Scenario: Fork failed
Given the GitHub API server:
"""
get('/repos/evilchelu/dotfiles') { '' }
post('/repos/evilchelu/dotfiles/forks') { halt 500 }
"""
When I run `hub fork`
Then the exit status should be 1
And the stderr should contain exactly:
"""
Error creating fork: Internal Server Error (HTTP 500)\n
"""
And there should be no "mislav" remote

Scenario: Fork already exists
Given the GitHub API server:
"""
get('/repos/evilchelu/dotfiles') { '' }
get('/repos/mislav/dotfiles') { '' }
"""
When I run `hub fork`
Then the exit status should be 1
And the stderr should contain exactly:
"""
Error creating fork: mislav/dotfiles already exists on github.com\n
"""
And there should be no "mislav" remote

Scenario: Invalid OAuth token
Given the GitHub API server:
"""
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' }
get('/repos/evilchelu/dotfiles') { '' }
"""
And I am "mislav" on github.com with OAuth token "WRONGTOKEN"
When I run `hub fork`
Then the exit status should be 1
And the stderr should contain exactly:
"""
Error creating fork: Unauthorized (HTTP 401)\n
"""

Scenario: HTTPS is preferred
Given the GitHub API server:
"""
get('/repos/evilchelu/dotfiles') { '' }
post('/repos/evilchelu/dotfiles/forks') { '' }
"""
And HTTPS is preferred
When I successfully run `hub fork`
Then the output should contain exactly "new remote: mislav\n"
And the url for "mislav" should be "https://github.com/mislav/dotfiles.git"

Scenario: Not in repo
Given the current dir is not a repo
When I run `hub fork`
Then the exit status should be 1
And the stderr should contain "fatal: Not a git repository"

Scenario: Unknown host
Given the "origin" remote has url "git@git.my.org:evilchelu/dotfiles.git"
When I run `hub fork`
Then the exit status should be 1
And the stderr should contain exactly:
"""
Error: repository under 'origin' remote is not a GitHub project\n
"""

Scenario: Enterprise fork
Given the GitHub API server:
"""
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN' }
get('/repos/evilchelu/dotfiles', :host_name => 'git.my.org') { '' }
post('/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') { '' }
"""
And the "origin" remote has url "git@git.my.org:evilchelu/dotfiles.git"
And I am "mislav" on git.my.org with OAuth token "FITOKEN"
And "git.my.org" is a whitelisted Enterprise host
When I successfully run `hub fork`
Then the url for "mislav" should be "git@git.my.org:mislav/dotfiles.git"
34 changes: 31 additions & 3 deletions features/steps.rb
@@ -1,3 +1,5 @@
require 'fileutils'

Given /^HTTPS is preferred$/ do
run_silent %(git config --global hub.protocol https)
end
Expand All @@ -11,12 +13,19 @@
end

Given /^the "([^"]*)" remote has url "([^"]*)"$/ do |remote_name, url|
run_silent %(git remote add #{remote_name} "#{url}")
remotes = run_silent('git remote').split("\n")
unless remotes.include? remote_name
run_silent %(git remote add #{remote_name} "#{url}")
else
run_silent %(git remote set-url #{remote_name} "#{url}")
end
end

Given /^I am "([^"]*)" on ([\w.-]+)$/ do |name, host|
Given /^I am "([^"]*)" on ([\w.-]+)(?: with OAuth token "([^"]*)")?$/ do |name, host, token|
edit_hub_config do |cfg|
cfg[host.downcase] = [{'user' => name}]
entry = {'user' => name}
entry['oauth_token'] = token if token
cfg[host.downcase] = [entry]
end
end

Expand All @@ -36,6 +45,20 @@
dirs.pop
end

Given /^the current dir is not a repo$/ do
in_current_dir do
FileUtils.rm_rf '.git'
end
end

Given /^the GitHub API server:$/ do |endpoints_str|
@server = Hub::LocalServer.start_sinatra do
eval endpoints_str, binding
end
# hit our Sinatra server instead of github.com
set_env 'HUB_TEST_HOST', "127.0.0.1:#{@server.port}"
end

Then /^"([^"]*)" should be run$/ do |cmd|
assert_command_run cmd
end
Expand Down Expand Up @@ -66,3 +89,8 @@
found = run_silent %(git config --get-all submodule."#{name}".url)
found.should eql(url)
end

Then /^there should be no "([^"]*)" remote$/ do |remote_name|
remotes = run_silent('git remote').split("\n")
remotes.should_not include(remote_name)
end
6 changes: 6 additions & 0 deletions features/support/env.rb
Expand Up @@ -24,10 +24,16 @@
set_env 'HOME', File.expand_path(File.join(current_dir, 'home'))
# used in fakebin/git
set_env 'HUB_SYSTEM_GIT', system_git
# ensure that api.github.com is actually never hit in tests
set_env 'HUB_TEST_HOST', '127.0.0.1:0'

FileUtils.mkdir_p ENV['HOME']
end

After do
@server.stop if defined? @server and @server
end

Before '~@noexec' do
set_env 'GIT', nil
end
Expand Down
11 changes: 8 additions & 3 deletions features/support/fakebin/git
Expand Up @@ -8,12 +8,17 @@ command="$1"

case "$command" in
"clone" | "fetch" | "pull" | "push" )
# don't actually execute these commands
exit
;;
* )
[ "$command $2 $3" == "remote add -f" ] && exit
# note: `submodule add` also initiates a clone, but we work around it
if [ "$command $2 $3" == "remote add -f" ]; then
subcommand=$2
shift 3
exec "$HUB_SYSTEM_GIT" $command $subcommand "$@"
else
exec "$HUB_SYSTEM_GIT" "$@"
fi
;;
esac

exec "$HUB_SYSTEM_GIT" "$@"
96 changes: 96 additions & 0 deletions features/support/local_server.rb
@@ -0,0 +1,96 @@
# based on <github.com/jnicklas/capybara/blob/ab62b27/lib/capybara/server.rb>
require 'net/http'

module Hub
class LocalServer
class Identify < Struct.new(:app)
def call(env)
if env["PATH_INFO"] == "/__identify__"
[200, {}, [app.object_id.to_s]]
else
app.call(env)
end
end
end

def self.ports
@ports ||= {}
end

def self.run_handler(app, port, &block)
begin
require 'rack/handler/thin'
Thin::Logging.silent = true
Rack::Handler::Thin.run(app, :Port => port, &block)
rescue LoadError
require 'rack/handler/webrick'
Rack::Handler::WEBrick.run(app, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0), &block)
end
end

def self.start_sinatra(&block)
require 'sinatra/base'
klass = Class.new(Sinatra::Base)
klass.set :environment, :test
klass.disable :protection
klass.class_eval(&block)

new(klass.new).start
end

attr_reader :app, :host, :port
attr_accessor :server

def initialize(app, host = '127.0.0.1')
@app = app
@host = host
@server = nil
@server_thread = nil
end

def responsive?
return false if @server_thread && @server_thread.join(0)

res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }

res.is_a?(Net::HTTPSuccess) and res.body == app.object_id.to_s
rescue Errno::ECONNREFUSED, Errno::EBADF
return false
end

def start
@port = self.class.ports[app.object_id]

if not @port or not responsive?
@port = find_available_port
self.class.ports[app.object_id] = @port

@server_thread = Thread.new do
self.class.run_handler(Identify.new(app), @port) { |server|
self.server = server
}
end

Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
end
rescue TimeoutError
raise "Rack application timed out during boot"
else
self
end

def stop
server.respond_to?(:stop!) ? server.stop! : server.stop
@server_thread.join
end

private

def find_available_port
server = TCPServer.new('127.0.0.1', 0)
server.addr[1]
ensure
server.close if server
end
end
end
18 changes: 16 additions & 2 deletions lib/hub/github_api.rb
Expand Up @@ -147,11 +147,14 @@ def post_form url, params
end

def perform_request url, type
url = URI.parse url unless url.respond_to? :hostname
url = URI.parse url unless url.respond_to? :host

require 'net/https'
req = Net::HTTP.const_get(type).new(url.request_uri)
http = create_connection(url)
# TODO: better naming?
http = configure_connection(req, url) do |host_url|
create_connection host_url
end

apply_authentication(req, url)
yield req if block_given?
Expand All @@ -162,6 +165,17 @@ def perform_request url, type
raise Context::FatalError, "error with #{type.to_s.upcase} #{url} (#{err.message})"
end

def configure_connection req, url
if ENV['HUB_TEST_HOST']
req['Host'] = url.host
url = url.dup
url.scheme = 'http'
url.host, test_port = ENV['HUB_TEST_HOST'].split(':')
url.port = test_port.to_i if test_port
end
yield url
end

def apply_authentication req, url
user = url.user || config.username(url.host)
pass = config.password(url.host, user)
Expand Down

0 comments on commit d1e03eb

Please sign in to comment.