gemcutter 0.2.1 → 0.3.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +22 -0
- data/Rakefile +52 -0
- data/lib/commands/abstract_command.rb +1 -3
- data/lib/commands/owner.rb +11 -1
- data/lib/commands/push.rb +2 -3
- data/lib/commands/webhook.rb +126 -0
- data/lib/rubygems_plugin.rb +1 -1
- data/test/abstract_command_test.rb +135 -0
- data/test/command_helper.rb +23 -7
- data/test/push_command_test.rb +57 -0
- data/test/webhook_command_test.rb +237 -0
- metadata +65 -7
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009, Nick Quaranto
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.test_files = FileList['test/*_command_test.rb']
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
gem.name = "gemcutter"
|
16
|
+
gem.version = "0.3.0.pre"
|
17
|
+
gem.summary = "Commands to interact with gemcutter.org"
|
18
|
+
gem.description = "Adds several commands to RubyGems for managing gems and more on Gemcutter.org."
|
19
|
+
gem.email = "nick@quaran.to"
|
20
|
+
gem.homepage = "http://gemcutter.org"
|
21
|
+
gem.authors = ["Nick Quaranto"]
|
22
|
+
gem.files = FileList["lib/rubygems_plugin.rb",
|
23
|
+
"lib/commands/*",
|
24
|
+
"test/*_helper.rb",
|
25
|
+
"test/*_test.rb",
|
26
|
+
"MIT-LICENSE",
|
27
|
+
"Rakefile"]
|
28
|
+
gem.test_files = []
|
29
|
+
gem.executables = []
|
30
|
+
gem.add_runtime_dependency('json_pure')
|
31
|
+
%w[rake shoulda activesupport webmock rr].each do |dep|
|
32
|
+
gem.add_development_dependency(dep)
|
33
|
+
end
|
34
|
+
gem.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if gem.respond_to? :required_rubygems_version=
|
35
|
+
gem.post_install_message = <<MESSAGE
|
36
|
+
|
37
|
+
========================================================================
|
38
|
+
|
39
|
+
Thanks for installing Gemcutter! You can now run:
|
40
|
+
|
41
|
+
gem push publish your gems for the world to use and enjoy
|
42
|
+
gem owner allow/disallow others to push to your gems
|
43
|
+
gem webhook register urls to be pinged when gems are pushed
|
44
|
+
|
45
|
+
========================================================================
|
46
|
+
|
47
|
+
MESSAGE
|
48
|
+
end
|
49
|
+
Jeweler::RubyforgeTasks.new
|
50
|
+
rescue LoadError
|
51
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
52
|
+
end
|
@@ -3,8 +3,6 @@ require 'rubygems/local_remote_options'
|
|
3
3
|
class Gem::AbstractCommand < Gem::Command
|
4
4
|
include Gem::LocalRemoteOptions
|
5
5
|
|
6
|
-
URL = "http://gemcutter.org"
|
7
|
-
|
8
6
|
def gemcutter_url
|
9
7
|
ENV['GEMCUTTER_URL'] || 'https://gemcutter.org'
|
10
8
|
end
|
@@ -15,7 +13,7 @@ class Gem::AbstractCommand < Gem::Command
|
|
15
13
|
end
|
16
14
|
|
17
15
|
def sign_in
|
18
|
-
say "Enter your Gemcutter credentials. Don't have an account yet? Create one at
|
16
|
+
say "Enter your Gemcutter credentials. Don't have an account yet? Create one at http://gemcutter.org/sign_up"
|
19
17
|
|
20
18
|
email = ask("Email: ")
|
21
19
|
password = ask_for_password("Password: ")
|
data/lib/commands/owner.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
class Gem::Commands::OwnerCommand < Gem::AbstractCommand
|
2
|
+
|
2
3
|
def description
|
3
4
|
'Manage gem owners on Gemcutter.'
|
4
5
|
end
|
5
6
|
|
7
|
+
def arguments
|
8
|
+
"GEM_NAME name of gem to manage owners for."
|
9
|
+
end
|
10
|
+
|
11
|
+
def usage
|
12
|
+
"#{program_name} GEM_NAME"
|
13
|
+
end
|
14
|
+
|
6
15
|
def initialize
|
7
16
|
super 'owner', description
|
8
17
|
defaults.merge!(:add => [], :remove => [])
|
@@ -57,7 +66,8 @@ class Gem::Commands::OwnerCommand < Gem::AbstractCommand
|
|
57
66
|
end end
|
58
67
|
|
59
68
|
def show_owners(name)
|
60
|
-
require 'json/pure'
|
69
|
+
require 'json/pure' unless defined?(JSON::JSON_LOADED)
|
70
|
+
|
61
71
|
response = make_request(:get, "gems/#{name}/owners.json") do |request|
|
62
72
|
request.add_field("Authorization", api_key)
|
63
73
|
end
|
data/lib/commands/push.rb
CHANGED
@@ -25,9 +25,9 @@ class Gem::Commands::PushCommand < Gem::AbstractCommand
|
|
25
25
|
def send_gem
|
26
26
|
say "Pushing gem to Gemcutter..."
|
27
27
|
|
28
|
-
|
28
|
+
path = get_one_gem_name
|
29
29
|
response = make_request(:post, "gems") do |request|
|
30
|
-
request.body =
|
30
|
+
request.body = Gem.read_binary(path)
|
31
31
|
request.add_field("Content-Length", request.body.size)
|
32
32
|
request.add_field("Content-Type", "application/octet-stream")
|
33
33
|
request.add_field("Authorization", api_key)
|
@@ -35,5 +35,4 @@ class Gem::Commands::PushCommand < Gem::AbstractCommand
|
|
35
35
|
|
36
36
|
say response.body
|
37
37
|
end
|
38
|
-
|
39
38
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
class Gem::Commands::WebhookCommand < Gem::AbstractCommand
|
2
|
+
|
3
|
+
def description
|
4
|
+
<<-EOF
|
5
|
+
Register a webhook that will be called any time a gem is updated on Gemcutter.
|
6
|
+
|
7
|
+
Webhooks can be created for either specific gems or all gems. In both cases
|
8
|
+
you'll get a POST request of the gem in JSON format at the URL you specify in
|
9
|
+
the command. You can also use this command to test fire a webhook.
|
10
|
+
EOF
|
11
|
+
end
|
12
|
+
|
13
|
+
def arguments
|
14
|
+
"GEM_NAME name of gem to register webhook for, or omit to list hooks."
|
15
|
+
end
|
16
|
+
|
17
|
+
def usage
|
18
|
+
"#{program_name} GEM_NAME"
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super 'webhook', description
|
23
|
+
option_text = "The URL of the webhook to"
|
24
|
+
|
25
|
+
add_option('-a', '--add URL', "#{option_text} add") do |value, options|
|
26
|
+
options[:send] = 'add'
|
27
|
+
options[:url] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
add_option('-r', '--remove URL', "#{option_text} remove") do |value, options|
|
31
|
+
options[:send] = 'remove'
|
32
|
+
options[:url] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
add_option('-f', '--fire URL', "#{option_text} testfire") do |value, options|
|
36
|
+
options[:send] = 'fire'
|
37
|
+
options[:url] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
add_option('-g', '--global', "Apply hook globally") do |value, options|
|
41
|
+
options[:global] = true
|
42
|
+
end
|
43
|
+
|
44
|
+
add_proxy_option
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute
|
48
|
+
setup
|
49
|
+
|
50
|
+
if options[:url]
|
51
|
+
name = options[:global] ? '*' : get_one_gem_name
|
52
|
+
send("#{options[:send]}_webhook", name, options[:url])
|
53
|
+
else
|
54
|
+
list_webhooks
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_webhook(name, url)
|
59
|
+
say "Adding webhook..."
|
60
|
+
make_webhook_request(:post, name, url)
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove_webhook(name, url)
|
64
|
+
say "Removing webhook..."
|
65
|
+
make_webhook_request(:delete, name, url, "web_hooks/remove")
|
66
|
+
end
|
67
|
+
|
68
|
+
def fire_webhook(name, url)
|
69
|
+
say "Test firing webhook..."
|
70
|
+
make_webhook_request(:post, name, url, "web_hooks/fire")
|
71
|
+
end
|
72
|
+
|
73
|
+
def list_webhooks
|
74
|
+
require 'json/pure' unless defined?(JSON::JSON_LOADED)
|
75
|
+
|
76
|
+
response = make_request(:get, "web_hooks") do |request|
|
77
|
+
request.add_field("Authorization", api_key)
|
78
|
+
end
|
79
|
+
|
80
|
+
case response
|
81
|
+
when Net::HTTPSuccess
|
82
|
+
begin
|
83
|
+
groups = JSON.parse(response.body)
|
84
|
+
|
85
|
+
if groups.size.zero?
|
86
|
+
say "You haven't added any webhooks yet."
|
87
|
+
else
|
88
|
+
groups.each do |group, hooks|
|
89
|
+
if options[:global]
|
90
|
+
next if group != "all gems"
|
91
|
+
elsif options[:args] && options[:args].first
|
92
|
+
next if group != options[:args].first
|
93
|
+
end
|
94
|
+
|
95
|
+
say "#{group}:"
|
96
|
+
hooks.each do |hook|
|
97
|
+
say "- #{hook['url']}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
rescue JSON::ParserError => json_error
|
102
|
+
say "There was a problem parsing the data:"
|
103
|
+
say json_error.to_s
|
104
|
+
terminate_interaction
|
105
|
+
end
|
106
|
+
else
|
107
|
+
say response.body
|
108
|
+
terminate_interaction
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def make_webhook_request(method, name, url, api = "web_hooks")
|
113
|
+
response = make_request(method, api) do |request|
|
114
|
+
request.set_form_data("gem_name" => name, "url" => url)
|
115
|
+
request.add_field("Authorization", api_key)
|
116
|
+
end
|
117
|
+
|
118
|
+
case response
|
119
|
+
when Net::HTTPSuccess
|
120
|
+
say response.body
|
121
|
+
else
|
122
|
+
say response.body
|
123
|
+
terminate_interaction
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/rubygems_plugin.rb
CHANGED
@@ -3,7 +3,7 @@ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
|
3
3
|
require 'rubygems/command_manager'
|
4
4
|
require 'commands/abstract_command'
|
5
5
|
|
6
|
-
%w[migrate owner push tumble].each do |command|
|
6
|
+
%w[migrate owner push tumble webhook].each do |command|
|
7
7
|
require "commands/#{command}"
|
8
8
|
Gem::CommandManager.instance.register_command command.to_sym
|
9
9
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'command_helper'
|
2
|
+
|
3
|
+
class Gem::Commands::FakeCommand < Gem::AbstractCommand
|
4
|
+
def description
|
5
|
+
'fake command'
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super 'fake', description
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class AbstractCommandTest < CommandTest
|
17
|
+
context "with an fake command" do
|
18
|
+
setup do
|
19
|
+
@command = Gem::Commands::FakeCommand.new
|
20
|
+
stub(@command).say
|
21
|
+
ENV['http_proxy'] = nil
|
22
|
+
ENV['HTTP_PROXY'] = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
context "parsing the proxy" do
|
26
|
+
should "return nil if no proxy is set" do
|
27
|
+
stub_config(:http_proxy => nil)
|
28
|
+
assert_equal nil, @command.http_proxy
|
29
|
+
end
|
30
|
+
|
31
|
+
should "return nil if the proxy is set to :no_proxy" do
|
32
|
+
stub_config(:http_proxy => :no_proxy)
|
33
|
+
assert_equal nil, @command.http_proxy
|
34
|
+
end
|
35
|
+
|
36
|
+
should "return a proxy as a URI if set" do
|
37
|
+
stub_config(:http_proxy => 'http://proxy.example.org:9192')
|
38
|
+
assert_equal 'proxy.example.org', @command.http_proxy.host
|
39
|
+
assert_equal 9192, @command.http_proxy.port
|
40
|
+
end
|
41
|
+
|
42
|
+
should "return a proxy as a URI if set by environment variable" do
|
43
|
+
ENV['http_proxy'] = "http://jack:duck@192.168.1.100:9092"
|
44
|
+
assert_equal "192.168.1.100", @command.http_proxy.host
|
45
|
+
assert_equal 9092, @command.http_proxy.port
|
46
|
+
assert_equal "jack", @command.http_proxy.user
|
47
|
+
assert_equal "duck", @command.http_proxy.password
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
should "sign in if no api key" do
|
52
|
+
stub(@command).api_key { nil }
|
53
|
+
stub(@command).sign_in
|
54
|
+
@command.setup
|
55
|
+
assert_received(@command) { |command| command.sign_in }
|
56
|
+
end
|
57
|
+
|
58
|
+
should "not sign in if api key exists" do
|
59
|
+
stub(@command).api_key { "1234567890" }
|
60
|
+
stub(@command).sign_in
|
61
|
+
@command.setup
|
62
|
+
assert_received(@command) { |command| command.sign_in.never }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "using the proxy" do
|
66
|
+
setup do
|
67
|
+
stub_config(:http_proxy => "http://gilbert:sekret@proxy.example.org:8081")
|
68
|
+
@proxy_class = Object.new
|
69
|
+
mock(Net::HTTP).Proxy('proxy.example.org', 8081, 'gilbert', 'sekret') { @proxy_class }
|
70
|
+
@command.use_proxy!
|
71
|
+
end
|
72
|
+
|
73
|
+
should "replace Net::HTTP with a proxy version" do
|
74
|
+
assert_equal @proxy_class, @command.proxy_class
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "signing in" do
|
79
|
+
setup do
|
80
|
+
@email = "email"
|
81
|
+
@password = "password"
|
82
|
+
@key = "key"
|
83
|
+
|
84
|
+
stub(@command).say
|
85
|
+
stub(@command).ask { @email }
|
86
|
+
stub(@command).ask_for_password { @password }
|
87
|
+
stub_config(:rubygems_api_key => @key)
|
88
|
+
end
|
89
|
+
|
90
|
+
context "on a good request" do
|
91
|
+
setup do
|
92
|
+
WebMock.stub_request(:get, "https://#{@email}:#{@password}@gemcutter.org/api/v1/api_key").to_return(:body => @key)
|
93
|
+
end
|
94
|
+
|
95
|
+
should "ask for email and password" do
|
96
|
+
@command.sign_in
|
97
|
+
assert_received(@command) { |command| command.ask("Email: ") }
|
98
|
+
assert_received(@command) { |command| command.ask_for_password("Password: ") }
|
99
|
+
end
|
100
|
+
|
101
|
+
should "say that we signed in" do
|
102
|
+
@command.sign_in
|
103
|
+
assert_received(@command) { |command| command.say("Signed in. Your api key has been stored in ~/.gem/credentials") }
|
104
|
+
assert_received(@command) { |command| command.say("Enter your Gemcutter credentials. Don't have an account yet? Create one at http://gemcutter.org/sign_up") }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "on a bad request" do
|
109
|
+
setup do
|
110
|
+
@problem = "Access Denied"
|
111
|
+
stub(@command).terminate_interaction
|
112
|
+
stub(@command).send(:api_key=)
|
113
|
+
WebMock.stub_request(:get, "https://#{@email}:#{@password}@gemcutter.org/api/v1/api_key").to_return(
|
114
|
+
:body => @problem,
|
115
|
+
:status => 401)
|
116
|
+
end
|
117
|
+
|
118
|
+
should "let the user know there was a problem" do
|
119
|
+
@command.sign_in
|
120
|
+
assert_received(@command) { |command| command.say(@problem) }
|
121
|
+
end
|
122
|
+
|
123
|
+
should "kill the command" do
|
124
|
+
@command.sign_in
|
125
|
+
assert_received(@command) { |command| command.terminate_interaction }
|
126
|
+
end
|
127
|
+
|
128
|
+
should "not write anything to the credentials file" do
|
129
|
+
@command.sign_in
|
130
|
+
assert_received(@command) { |command| command.send(:api_key=).never }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/test/command_helper.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
3
|
|
4
|
-
require "#{File.dirname(__FILE__)}/../../vendor/bundler_gems/environment"
|
5
|
-
|
6
4
|
require 'shoulda'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/test_case'
|
7
|
+
require 'webmock'
|
8
|
+
require 'rr'
|
9
|
+
|
7
10
|
begin
|
8
11
|
require 'redgreen'
|
9
12
|
rescue LoadError
|
10
13
|
end
|
11
|
-
require 'active_support'
|
12
|
-
require 'active_support/test_case'
|
13
|
-
require 'fakeweb'
|
14
|
-
require 'rr'
|
15
14
|
|
16
|
-
|
15
|
+
WebMock.disable_net_connect!
|
17
16
|
|
18
17
|
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
19
18
|
|
@@ -21,6 +20,11 @@ require "rubygems_plugin"
|
|
21
20
|
|
22
21
|
class CommandTest < ActiveSupport::TestCase
|
23
22
|
include RR::Adapters::TestUnit unless include?(RR::Adapters::TestUnit)
|
23
|
+
include WebMock
|
24
|
+
|
25
|
+
def teardown
|
26
|
+
reset_webmock
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def stub_config(config)
|
@@ -28,3 +32,15 @@ def stub_config(config)
|
|
28
32
|
config.each { |key, value| file[key] = value }
|
29
33
|
stub(Gem).configuration { file }
|
30
34
|
end
|
35
|
+
|
36
|
+
def assert_said(command, what)
|
37
|
+
assert_received(command) do |command|
|
38
|
+
command.say(what)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def assert_never_said(command, what)
|
43
|
+
assert_received(command) do |command|
|
44
|
+
command.say(what).never
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'command_helper'
|
2
|
+
|
3
|
+
class PushCommandTest < CommandTest
|
4
|
+
context "pushing" do
|
5
|
+
setup do
|
6
|
+
@command = Gem::Commands::PushCommand.new
|
7
|
+
stub(@command).say
|
8
|
+
end
|
9
|
+
|
10
|
+
should "setup and send the gem" do
|
11
|
+
mock(@command).setup
|
12
|
+
mock(@command).send_gem
|
13
|
+
@command.execute
|
14
|
+
assert_received(@command) { |command| command.setup }
|
15
|
+
assert_received(@command) { |command| command.send_gem }
|
16
|
+
end
|
17
|
+
|
18
|
+
should "raise an error with no arguments" do
|
19
|
+
assert_raise Gem::CommandLineError do
|
20
|
+
@command.send_gem
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "pushing a gem" do
|
25
|
+
setup do
|
26
|
+
@url = "https://gemcutter.org/api/v1/gems"
|
27
|
+
@gem_path = "path/to/foo-0.0.0.gem"
|
28
|
+
@gem_binary = StringIO.new("gem")
|
29
|
+
|
30
|
+
stub(@command).say
|
31
|
+
stub(@command).options { {:args => [@gem_path]} }
|
32
|
+
stub(Gem).read_binary(@gem_path) { @gem_binary }
|
33
|
+
stub_config({ :rubygems_api_key => "key" })
|
34
|
+
WebMock.stub_request(:post, @url).to_return(:body => "Success!")
|
35
|
+
|
36
|
+
@command.send_gem
|
37
|
+
end
|
38
|
+
|
39
|
+
should "say push was successful" do
|
40
|
+
assert_received(@command) { |command| command.say("Pushing gem to Gemcutter...") }
|
41
|
+
assert_received(@command) { |command| command.say("Success!") }
|
42
|
+
end
|
43
|
+
|
44
|
+
should "post to api" do
|
45
|
+
# webmock doesn't pass body params on correctly :[
|
46
|
+
WebMock.assert_requested(:post, @url,
|
47
|
+
:times => 1)
|
48
|
+
WebMock.assert_requested(:post, @url,
|
49
|
+
:headers => { 'Authorization' => 'key' })
|
50
|
+
WebMock.assert_requested(:post, @url,
|
51
|
+
:headers => { 'Content-Length' => @gem_binary.size })
|
52
|
+
WebMock.assert_requested(:post, @url,
|
53
|
+
:headers => { 'Content-Type' => 'application/octet-stream' })
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'command_helper'
|
2
|
+
|
3
|
+
class WebhookCommandTest < CommandTest
|
4
|
+
context "webhooking" do
|
5
|
+
setup do
|
6
|
+
@gem = "foo"
|
7
|
+
@api = "https://gemcutter.org/api/v1/web_hooks"
|
8
|
+
@url = "http://example.com/hook"
|
9
|
+
@command = Gem::Commands::WebhookCommand.new
|
10
|
+
stub(@command).say
|
11
|
+
end
|
12
|
+
|
13
|
+
%w[-a --add -r --remove -f --fire].each do |option|
|
14
|
+
should "raise an error with no URL with #{option}" do
|
15
|
+
assert_raise OptionParser::MissingArgument do
|
16
|
+
@command.handle_options([@gem, option])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "adding a specific hook" do
|
22
|
+
setup do
|
23
|
+
stub_config({ :rubygems_api_key => "key" })
|
24
|
+
stub_request(:post, @api).to_return(:body => "Success!")
|
25
|
+
|
26
|
+
@command.handle_options([@gem, "-a", @url])
|
27
|
+
@command.execute
|
28
|
+
end
|
29
|
+
|
30
|
+
should "say hook was added" do
|
31
|
+
assert_received(@command) do |command|
|
32
|
+
command.say("Adding webhook...")
|
33
|
+
command.say("Success!")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
should "post to api" do
|
38
|
+
# webmock doesn't pass body params on correctly :[
|
39
|
+
assert_requested(:post, @api,
|
40
|
+
:times => 1)
|
41
|
+
assert_requested(:post, @api,
|
42
|
+
:headers => { 'Authorization' => 'key' })
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "adding a global hook" do
|
47
|
+
setup do
|
48
|
+
stub_config({ :rubygems_api_key => "key" })
|
49
|
+
stub_request(:post, @api).to_return(:body => "Success!")
|
50
|
+
|
51
|
+
@command.handle_options(["-g", "-a", @url])
|
52
|
+
@command.execute
|
53
|
+
end
|
54
|
+
|
55
|
+
should "say hook was added" do
|
56
|
+
assert_received(@command) do |command|
|
57
|
+
command.say("Adding webhook...")
|
58
|
+
command.say("Success!")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should "post to api" do
|
63
|
+
# webmock doesn't pass body params on correctly :[
|
64
|
+
assert_requested(:post, @api,
|
65
|
+
:times => 1)
|
66
|
+
assert_requested(:post, @api,
|
67
|
+
:headers => { 'Authorization' => 'key' })
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "listing hooks with some available" do
|
72
|
+
setup do
|
73
|
+
stub_config({ :rubygems_api_key => "key" })
|
74
|
+
stub_request(:get, @api).to_return :body => <<EOF
|
75
|
+
{
|
76
|
+
"foo": [{"url":"http://foogemhook.com","failure_count":0}],
|
77
|
+
"all gems":[{"url":"http://allgemshook.com","failure_count":0}]
|
78
|
+
}
|
79
|
+
EOF
|
80
|
+
end
|
81
|
+
|
82
|
+
should "list all hooks" do
|
83
|
+
@command.handle_options([])
|
84
|
+
@command.execute
|
85
|
+
|
86
|
+
assert_said(@command, "all gems:")
|
87
|
+
assert_said(@command, "- http://allgemshook.com")
|
88
|
+
assert_said(@command, "foo:")
|
89
|
+
assert_said(@command, "- http://foogemhook.com")
|
90
|
+
end
|
91
|
+
|
92
|
+
should "list only specific hooks" do
|
93
|
+
@command.handle_options(['foo'])
|
94
|
+
@command.execute
|
95
|
+
|
96
|
+
assert_said(@command, "foo:")
|
97
|
+
assert_said(@command, "- http://foogemhook.com")
|
98
|
+
assert_never_said(@command, "all gems:")
|
99
|
+
assert_never_said(@command, "- http://allgemshook.com")
|
100
|
+
end
|
101
|
+
|
102
|
+
should "list only global hooks" do
|
103
|
+
@command.handle_options(['-g'])
|
104
|
+
@command.execute
|
105
|
+
|
106
|
+
assert_said(@command, "all gems:")
|
107
|
+
assert_said(@command, "- http://allgemshook.com")
|
108
|
+
assert_never_said(@command, "foo:")
|
109
|
+
assert_never_said(@command, "- http://foogemhook.com")
|
110
|
+
end
|
111
|
+
|
112
|
+
should "send get to api" do
|
113
|
+
@command.handle_options([])
|
114
|
+
@command.execute
|
115
|
+
|
116
|
+
# webmock doesn't pass body params on correctly :[
|
117
|
+
assert_requested(:get, @api,
|
118
|
+
:times => 1)
|
119
|
+
assert_requested(:get, @api,
|
120
|
+
:headers => { 'Authorization' => 'key' })
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "listing hooks with none available" do
|
125
|
+
setup do
|
126
|
+
stub_config({ :rubygems_api_key => "key" })
|
127
|
+
stub_request(:get, @api).to_return(:body => "{}")
|
128
|
+
@command.handle_options([])
|
129
|
+
@command.execute
|
130
|
+
end
|
131
|
+
|
132
|
+
should "list hooks" do
|
133
|
+
assert_received(@command) do |command|
|
134
|
+
command.say("You haven't added any webhooks yet.")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "listing hooks with a json error" do
|
140
|
+
setup do
|
141
|
+
stub(@command).terminate_interaction
|
142
|
+
stub_config({ :rubygems_api_key => "key" })
|
143
|
+
stub_request(:get, @api).to_return(:body => "fubar")
|
144
|
+
@command.handle_options([])
|
145
|
+
@command.execute
|
146
|
+
end
|
147
|
+
|
148
|
+
should "dump out with error message" do
|
149
|
+
assert_received(@command) do |command|
|
150
|
+
command.say("There was a problem parsing the data:")
|
151
|
+
command.say(/unexpected token at 'fubar'/)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
should "terminate interaction" do
|
156
|
+
assert_received(@command) do |command|
|
157
|
+
command.terminate_interaction
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "removing specific hooks" do
|
163
|
+
setup do
|
164
|
+
stub_config({ :rubygems_api_key => "key" })
|
165
|
+
stub_request(:delete, "#{@api}/remove").to_return(:body => "Success!")
|
166
|
+
|
167
|
+
@command.handle_options([@gem, "-r", @url])
|
168
|
+
@command.execute
|
169
|
+
end
|
170
|
+
|
171
|
+
should "say hook was removed" do
|
172
|
+
assert_received(@command) do |command|
|
173
|
+
command.say("Removing webhook...")
|
174
|
+
command.say("Success!")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
should "send delete to api" do
|
179
|
+
# webmock doesn't pass body params on correctly :[
|
180
|
+
assert_requested(:delete, "#{@api}/remove",
|
181
|
+
:times => 1)
|
182
|
+
assert_requested(:delete, "#{@api}/remove",
|
183
|
+
:headers => { 'Authorization' => 'key' })
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "removing global hooks" do
|
188
|
+
setup do
|
189
|
+
stub_config({ :rubygems_api_key => "key" })
|
190
|
+
stub_request(:delete, "#{@api}/remove").to_return(:body => "Success!")
|
191
|
+
|
192
|
+
@command.handle_options(["-g", "-r", @url])
|
193
|
+
@command.execute
|
194
|
+
end
|
195
|
+
|
196
|
+
should "say hook was removed" do
|
197
|
+
assert_received(@command) do |command|
|
198
|
+
command.say("Removing webhook...")
|
199
|
+
command.say("Success!")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
should "send delete to api" do
|
204
|
+
# webmock doesn't pass body params on correctly :[
|
205
|
+
assert_requested(:delete, "#{@api}/remove",
|
206
|
+
:times => 1)
|
207
|
+
assert_requested(:delete, "#{@api}/remove",
|
208
|
+
:headers => { 'Authorization' => 'key' })
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context "test firing hooks" do
|
213
|
+
setup do
|
214
|
+
stub_config({ :rubygems_api_key => "key" })
|
215
|
+
stub_request(:post, "#{@api}/fire").to_return(:body => "Success!")
|
216
|
+
|
217
|
+
@command.handle_options([@gem, "-f", @url])
|
218
|
+
@command.execute
|
219
|
+
end
|
220
|
+
|
221
|
+
should "say hook was fired" do
|
222
|
+
assert_received(@command) do |command|
|
223
|
+
command.say("Test firing webhook...")
|
224
|
+
command.say("Success!")
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
should "send post to api" do
|
229
|
+
# webmock doesn't pass body params on correctly :[
|
230
|
+
assert_requested(:post, "#{@api}/fire",
|
231
|
+
:times => 1)
|
232
|
+
assert_requested(:post, "#{@api}/fire",
|
233
|
+
:headers => { 'Authorization' => 'key' })
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gemcutter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Quaranto
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-03 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,7 +22,57 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: "0"
|
24
24
|
version:
|
25
|
-
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: shoulda
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: activesupport
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webmock
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: rr
|
67
|
+
type: :development
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
description: Adds several commands to RubyGems for managing gems and more on Gemcutter.org.
|
26
76
|
email: nick@quaran.to
|
27
77
|
executables: []
|
28
78
|
|
@@ -31,14 +81,21 @@ extensions: []
|
|
31
81
|
extra_rdoc_files: []
|
32
82
|
|
33
83
|
files:
|
84
|
+
- MIT-LICENSE
|
85
|
+
- Rakefile
|
34
86
|
- lib/commands/abstract_command.rb
|
35
87
|
- lib/commands/migrate.rb
|
36
88
|
- lib/commands/owner.rb
|
37
89
|
- lib/commands/push.rb
|
38
90
|
- lib/commands/tumble.rb
|
91
|
+
- lib/commands/webhook.rb
|
39
92
|
- lib/rubygems_plugin.rb
|
93
|
+
- test/abstract_command_test.rb
|
94
|
+
- test/command_helper.rb
|
95
|
+
- test/push_command_test.rb
|
96
|
+
- test/webhook_command_test.rb
|
40
97
|
has_rdoc: true
|
41
|
-
homepage: http://
|
98
|
+
homepage: http://gemcutter.org
|
42
99
|
licenses: []
|
43
100
|
|
44
101
|
post_install_message: |+
|
@@ -49,6 +106,7 @@ post_install_message: |+
|
|
49
106
|
|
50
107
|
gem push publish your gems for the world to use and enjoy
|
51
108
|
gem owner allow/disallow others to push to your gems
|
109
|
+
gem webhook register urls to be pinged when gems are pushed
|
52
110
|
|
53
111
|
========================================================================
|
54
112
|
|
@@ -70,10 +128,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
128
|
version:
|
71
129
|
requirements: []
|
72
130
|
|
73
|
-
rubyforge_project:
|
131
|
+
rubyforge_project:
|
74
132
|
rubygems_version: 1.3.5
|
75
133
|
signing_key:
|
76
134
|
specification_version: 3
|
77
135
|
summary: Commands to interact with gemcutter.org
|
78
|
-
test_files:
|
79
|
-
|
136
|
+
test_files: []
|
137
|
+
|