github-downloads 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubyforge
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "mocha"
7
+ gem "rspec"
8
+ gem "mimic"
9
+ gem "awesome_print"
10
+ end
11
+
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ github-downloads (0.1.0)
5
+ highline (~> 1.6.2)
6
+ hirb (~> 0.4.5)
7
+ json (~> 1.5.3)
8
+ mime-types (~> 1.16.0)
9
+ rest-client (~> 1.6.3)
10
+ simpleconsole (~> 0.1.1)
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ awesome_print (0.3.1)
16
+ diff-lcs (1.1.2)
17
+ highline (1.6.2)
18
+ hirb (0.4.5)
19
+ json (1.5.3)
20
+ mime-types (1.16)
21
+ mimic (0.4.1)
22
+ json
23
+ plist
24
+ rack
25
+ sinatra
26
+ mocha (0.9.10)
27
+ rake
28
+ plist (3.1.0)
29
+ rack (1.2.1)
30
+ rake (0.9.2)
31
+ rest-client (1.6.3)
32
+ mime-types (>= 1.16)
33
+ rspec (2.5.0)
34
+ rspec-core (~> 2.5.0)
35
+ rspec-expectations (~> 2.5.0)
36
+ rspec-mocks (~> 2.5.0)
37
+ rspec-core (2.5.2)
38
+ rspec-expectations (2.5.0)
39
+ diff-lcs (~> 1.1.2)
40
+ rspec-mocks (2.5.0)
41
+ simpleconsole (0.1.1)
42
+ sinatra (1.1.2)
43
+ rack (~> 1.1)
44
+ tilt (~> 1.2)
45
+ tilt (1.2.2)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ awesome_print
52
+ github-downloads!
53
+ mimic
54
+ mocha
55
+ rspec
@@ -0,0 +1,25 @@
1
+ # github-downloads - a gem for managing your downloads, on Github
2
+
3
+ This is a simple gem that does exactly what it says on the tin: manages your Github project downloads.
4
+
5
+ $ gem install github-downloads
6
+
7
+ It can be used to list the downloads for your project:
8
+
9
+ $ github-downloads list -u lukeredpath -r simpleconfig
10
+
11
+ It can also be used to create a new download:
12
+
13
+ $ github-downloads create -u lukeredpath -r simpleconfig -f ~/myproject/somefile.zip -d "This is an important file"
14
+
15
+ ### Github::Client, a generic Github API client
16
+
17
+ The gem is built on top of [version 3 of the Github API](http://developer.github.com/v3/); included in the source is a class, Github::Client, a simple wrapper around the Github API that uses RestClient and simplifies communication with the Github API.
18
+
19
+ It provides a simple interface to the Github REST API. It handles errors appropriately and returns parsed response data as well as additional metadata such as API rate limits.
20
+
21
+ It doesn't aim to be a full-blown Github API library (completely with a local domain model) but a very thin utility ckass that can be used to write other simple scripts and gems, like this one. Feel free to use this in your own project if you are working with the Github API.
22
+
23
+ ### License
24
+
25
+ This code is provided under the terms of the MIT license.
@@ -0,0 +1,102 @@
1
+ require 'rake'
2
+ require 'rdoc/task'
3
+ require "bundler/setup"
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run all examples"
7
+ RSpec::Core::RakeTask.new(:specs)
8
+
9
+ task :default => :specs
10
+
11
+ require "rubygems"
12
+ require "rubygems/package_task"
13
+
14
+ GEM_VERSION = "0.1.0"
15
+
16
+ # This builds the actual gem. For details of what all these options
17
+ # mean, and other ones you can add, check the documentation here:
18
+ #
19
+ # http://rubygems.org/read/chapter/20
20
+ #
21
+ spec = Gem::Specification.new do |s|
22
+
23
+ # Change these as appropriate
24
+ s.name = "github-downloads"
25
+ s.version = GEM_VERSION
26
+ s.summary = "Manages downloads for your Github projects"
27
+ s.author = "Luke Redpath"
28
+ s.email = "luke@lukeredpath.co.uk"
29
+ s.homepage = "http://lukeredpath.co.uk"
30
+
31
+ s.has_rdoc = false
32
+ # s.extra_rdoc_files = %w(README)
33
+ # s.rdoc_options = %w(--main README)
34
+
35
+ # Add any extra files to include in the gem
36
+ s.files = %w(Gemfile Gemfile.lock Rakefile README.md) + Dir.glob("{bin,spec,lib}/**/*")
37
+ s.executables = FileList["bin/**"].map { |f| File.basename(f) }
38
+ s.require_paths = ["lib"]
39
+
40
+ # If you want to depend on other gems, add them here, along with any
41
+ # relevant versions
42
+ gem "rest-client"
43
+ gem "json"
44
+ gem "simpleconsole"
45
+ gem "hirb"
46
+ gem "mime-types"
47
+ gem "highline"
48
+
49
+ s.add_dependency("rest-client", "~> 1.6.3")
50
+ s.add_dependency("json", "~> 1.5.3")
51
+ s.add_dependency("simpleconsole", "~> 0.1.1")
52
+ s.add_dependency("hirb", "~> 0.4.5")
53
+ s.add_dependency("mime-types", "~> 1.16.0")
54
+ s.add_dependency("highline", "~> 1.6.2")
55
+
56
+ # If your tests use any gems, include them here
57
+ s.add_development_dependency("rspec")
58
+ s.add_development_dependency("mocha")
59
+ s.add_development_dependency("mimic")
60
+ s.add_development_dependency("awesome_print")
61
+ end
62
+
63
+ # This task actually builds the gem. We also regenerate a static
64
+ # .gemspec file, which is useful if something (i.e. GitHub) will
65
+ # be automatically building a gem for this project. If you're not
66
+ # using GitHub, edit as appropriate.
67
+ #
68
+ # To publish your gem online, install the 'gemcutter' gem; Read more
69
+ # about that here: http://gemcutter.org/pages/gem_docs
70
+ Gem::PackageTask.new(spec) do |pkg|
71
+ pkg.gem_spec = spec
72
+ end
73
+
74
+ desc "Build the gemspec file #{spec.name}.gemspec"
75
+ task :gemspec do
76
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
77
+ File.open(file, "w") {|f| f << spec.to_ruby }
78
+ end
79
+
80
+ # If you don't want to generate the .gemspec file, just remove this line. Reasons
81
+ # why you might want to generate a gemspec:
82
+ # - using bundler with a git source
83
+ # - building the gem without rake (i.e. gem build blah.gemspec)
84
+ # - maybe others?
85
+ task :package => :gemspec
86
+
87
+ # Generate documentation
88
+ RDoc::Task.new do |rd|
89
+ rd.main = "README"
90
+ rd.rdoc_files.include("README", "lib/**/*.rb")
91
+ rd.rdoc_dir = "rdoc"
92
+ end
93
+
94
+ desc 'Clear out RDoc and generated packages'
95
+ task :clean => [:clobber_rdoc, :clobber_package] do
96
+ rm "#{spec.name}.gemspec"
97
+ end
98
+
99
+ desc 'Publish gem'
100
+ task :publish => :gem do
101
+ system "gem push pkg/#{spec.file_name}"
102
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'simpleconsole'
3
+ require File.join(File.dirname(__FILE__), *%w[.. lib github])
4
+
5
+ SimpleConsole::Application.run(ARGV, Github::DownloadsController)
@@ -0,0 +1,2 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'github/downloads_controller'
@@ -0,0 +1,142 @@
1
+ require 'restclient'
2
+ require 'ostruct'
3
+ require 'json'
4
+
5
+ module Github
6
+ class Client
7
+ class << self
8
+ def connect(base_url, user = nil, pass = nil)
9
+ new RestClient::Resource.new(base_url, user, pass)
10
+ end
11
+
12
+ def proxy=(proxy_host)
13
+ RestClient.proxy = proxy_host
14
+ end
15
+ end
16
+
17
+ def initialize(resource)
18
+ @resource = resource
19
+ end
20
+
21
+ def get(path)
22
+ request :get, path
23
+ end
24
+
25
+ def post(path, data)
26
+ request :post, path, data
27
+ end
28
+
29
+ def put(path, data)
30
+ request :put, path, data
31
+ end
32
+
33
+ def delete(path)
34
+ request :delete, path
35
+ end
36
+
37
+ def head(path)
38
+ request :head, path
39
+ end
40
+
41
+ private
42
+
43
+ def request(method, path, data = nil)
44
+ if data
45
+ Response.new(@resource[path].send(method, data.to_json))
46
+ else
47
+ Response.new(@resource[path].send(method))
48
+ end
49
+ rescue RestClient::Exception => e
50
+ if Response.can_handle?(e.response)
51
+ Response.new(e.response)
52
+ else
53
+ raise e
54
+ end
55
+ end
56
+
57
+ class Response
58
+ SUCCESS_CODES = [200, 201]
59
+ FAILURE_CODES = [400, 401, 422]
60
+
61
+ def initialize(response)
62
+ @response = response
63
+ end
64
+
65
+ def self.can_handle?(response)
66
+ (SUCCESS_CODES + FAILURE_CODES).include?(response.code)
67
+ end
68
+
69
+ def to_s
70
+ "#{@response.description.strip} | Rate limit: #{rate_limit_remaining} / #{rate_limit_remaining}"
71
+ end
72
+
73
+ def success?
74
+ when_statuses_are *SUCCESS_CODES
75
+ end
76
+
77
+ def error?
78
+ when_statuses_are *FAILURE_CODES
79
+ end
80
+
81
+ def error_message
82
+ parsed_response["message"]
83
+ end
84
+
85
+ def errors
86
+ parsed_response["errors"].map { |hash| EntityError.new(hash) }
87
+ end
88
+
89
+ def rate_limit
90
+ @response.headers[:x_ratelimit_limit].to_i
91
+ end
92
+
93
+ def rate_limit_remaining
94
+ @response.headers[:x_ratelimit_remaining].to_i
95
+ end
96
+
97
+ def location
98
+ @response.headers[:location]
99
+ end
100
+
101
+ def data
102
+ parsed_response
103
+ end
104
+
105
+ private
106
+
107
+ def parsed_response
108
+ @parsed_response ||= JSON.parse(@response)
109
+ end
110
+
111
+ def when_statuses_are(*codes)
112
+ codes.include?(@response.code)
113
+ end
114
+ end
115
+
116
+ class EntityError < OpenStruct
117
+ MISSING = "missing"
118
+ MISSING_FIELD = "missing_field"
119
+ INVALID = "invalid"
120
+ ALREADY_EXISTS = "already_exists"
121
+
122
+ def to_s
123
+ "<Github::Client::EntityError: #{description}>"
124
+ end
125
+
126
+ def description
127
+ case self.code
128
+ when MISSING
129
+ "#{self.resource} is missing"
130
+ when MISSING_FIELD
131
+ "#{self.resource}##{self.field} is missing"
132
+ when INVALID
133
+ "#{self.resource}##{self.field} is invalid (refer to documentation)"
134
+ when ALREADY_EXISTS
135
+ "#{self.resource}##{self.field} value is already taken (must be unique)"
136
+ else
137
+ "#{self.resource}##{self.field} error: #{self.code}"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,66 @@
1
+ require 'ostruct'
2
+ require 'mime/types'
3
+ require 'github/client'
4
+
5
+ module Github
6
+ class Downloads
7
+ attr_accessor :uploader
8
+
9
+ GITHUB_BASE_URL = "https://api.github.com"
10
+
11
+ def initialize(client, user, repos)
12
+ @client = client
13
+ @user = user
14
+ @repos = repos
15
+ end
16
+
17
+ def self.connect(user, password, repos)
18
+ client = Client.connect(GITHUB_BASE_URL, user, password)
19
+ new(client, user, repos)
20
+ end
21
+
22
+ class UnexpectedResponse < StandardError
23
+ attr_reader :response
24
+
25
+ def initialize(response)
26
+ @response = response
27
+ end
28
+ end
29
+
30
+ def list
31
+ response = @client.get(downloads_resource_path)
32
+
33
+ if response.success?
34
+ from_response_data(response.data)
35
+ else
36
+ raise UnexpectedResponse, response
37
+ end
38
+ end
39
+
40
+ def create(file_path, description = "")
41
+ response = @client.post(downloads_resource_path, {
42
+ :name => File.basename(file_path),
43
+ :description => description,
44
+ :content_type => MIME::Types.type_for(file_path)[0] || MIME::Types["application/octet-stream"][0],
45
+ :size => File.size?(file_path)
46
+ })
47
+
48
+ if response.success?
49
+ uploader.upload(File.expand_path(file_path), response.data)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def downloads_resource_path
56
+ "/repos/#{@user}/#{@repos}/downloads"
57
+ end
58
+
59
+ def from_response_data(data)
60
+ data.map { |hash| Download.new(hash) }
61
+ end
62
+
63
+ class Download < OpenStruct
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,109 @@
1
+ require 'simpleconsole'
2
+ require 'hirb'
3
+ require 'highline/import'
4
+ require 'osx_keychain'
5
+
6
+ require 'github/downloads'
7
+ require 'github/s3_uploader'
8
+
9
+ module Github
10
+ class DownloadsController < SimpleConsole::Controller
11
+ include Hirb::Console
12
+
13
+ params :string => {
14
+ :u => :user,
15
+ :f => :file,
16
+ :n => :name,
17
+ :r => :repos,
18
+ :d => :description,
19
+ :p => :proxy
20
+ },
21
+ :bool => {
22
+ :o => :overwrite
23
+ }
24
+
25
+ before_filter :set_proxy
26
+
27
+ def default
28
+ puts "Valid actions: list, upload, delete"
29
+ exit 1
30
+ end
31
+
32
+ def list
33
+ if (downloads = github.list)
34
+ table downloads, {:fields => [:name, :description, :download_count]}
35
+ else
36
+ puts "Couldn't fetch downloads!"
37
+ end
38
+ rescue Github::Downloads::UnexpectedResponse => e
39
+ fail! "Unexpected response (#{e.response})."
40
+ end
41
+
42
+ def create
43
+ if params[:file].nil?
44
+ fail! "* A file must be specified (-f or --file)"
45
+ end
46
+
47
+ if (url_for_upload = github(:authenticated).create(params[:file], params[:description]))
48
+ puts "Upload successful! (#{url_for_upload})"
49
+ else
50
+ fail! "Upload failed!"
51
+ end
52
+ rescue Github::Downloads::UnexpectedResponse => e
53
+ fail! "Unexpected response (#{e.response})."
54
+ end
55
+
56
+ private
57
+
58
+ def fail!(message, status = 1)
59
+ puts(message)
60
+ exit(status)
61
+ end
62
+
63
+ def fetch_downloads(repo)
64
+ Hpricot(open("https://github.com/#{repo}/downloads"))
65
+ end
66
+
67
+ def set_proxy
68
+ Github::Client.proxy = params[:proxy]
69
+ end
70
+
71
+ def github(authentication_required = false)
72
+ if params[:repos].nil? || params[:user].nil?
73
+ fail! "* Please specify a user (-u or --user) and repository (-r or --repos)"
74
+ end
75
+
76
+ if authentication_required
77
+ password = lookup_or_prompt_for_password(params[:user])
78
+ end
79
+
80
+ @github ||= Github::Downloads.connect(params[:user], password, params[:repos]).tap do |gh|
81
+ gh.uploader = Github::S3Uploader.new
82
+ end
83
+ end
84
+
85
+ def lookup_or_prompt_for_password(user)
86
+ fetch_password_from_keychain(user) || prompt_for_password(user)
87
+ end
88
+
89
+ def prompt_for_password(user)
90
+ ask("Enter Github password:") {|q| q.echo = false }.tap do |password|
91
+ store_password_in_keychain(user, password)
92
+ end
93
+ end
94
+
95
+ def mac?
96
+ RUBY_PLATFORM.match(/darwin/)
97
+ end
98
+
99
+ KEYCHAIN_SERVICE = "github-uploads"
100
+
101
+ def fetch_password_from_keychain(user)
102
+ OSXKeychain.new[KEYCHAIN_SERVICE, user] if mac?
103
+ end
104
+
105
+ def store_password_in_keychain(user, password)
106
+ OSXKeychain.new[KEYCHAIN_SERVICE, user] = password if mac?
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,24 @@
1
+ require 'restclient'
2
+
3
+ module Github
4
+ class S3Uploader
5
+ def upload(path, metadata)
6
+ response = RestClient.post(metadata["s3_url"], [
7
+ ["key", "#{metadata["prefix"]}#{metadata["name"]}"],
8
+ ["acl", metadata["acl"]],
9
+ ["success_action_status", 201],
10
+ ["Filename", metadata["name"]],
11
+ ["AWSAccessKeyId", metadata["accesskeyid"]],
12
+ ["Policy", metadata["policy"]],
13
+ ["Signature", metadata["signature"]],
14
+ ["Content-Type", metadata["mime_type"]],
15
+ ["file", File.open(path)]
16
+ ])
17
+ if response.code == 201
18
+ metadata["url"]
19
+ else
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
File without changes
@@ -0,0 +1,211 @@
1
+ require 'spec_helper'
2
+ require 'github/client'
3
+
4
+ class FakeResource # fake RestClient::Resource
5
+ attr_reader :path
6
+
7
+ def initialize(path = "/")
8
+ @path = Mocha::Mockery.instance.new_state_machine("location").starts_as(path)
9
+ end
10
+
11
+ def [](path)
12
+ tap { @path.become(path) }
13
+ end
14
+ end
15
+
16
+ describe "Github::Client" do
17
+
18
+ before :each do
19
+ @resource = FakeResource.new
20
+ @client = Github::Client.new(@resource)
21
+ end
22
+
23
+ describe "#get" do
24
+ it "performs a GET request to /some/path on the resource" do
25
+ @resource.expects(:get).when @resource.path.is("/some/path")
26
+ @client.get("/some/path")
27
+ end
28
+
29
+ it "returns a Github::Client::Response" do
30
+ @resource.stubs(:get)
31
+ @client.get("/some/path").should be_instance_of(Github::Client::Response)
32
+ end
33
+ end
34
+
35
+ describe "#post" do
36
+ it "performs a POST request with the supplied data in JSON format to /some/path on the resource" do
37
+ @resource.expects(:post).with({"foo" => "bar"}.to_json).when @resource.path.is("/some/path")
38
+ @client.post("/some/path", {"foo" => "bar"})
39
+ end
40
+
41
+ it "returns a Github::Client::Response" do
42
+ @resource.stubs(:post)
43
+ @client.post("/some/path", nil).should be_instance_of(Github::Client::Response)
44
+ end
45
+ end
46
+
47
+ describe "#put" do
48
+ it "performs a PUT request with the supplied data in JSON format to /some/path on the resource" do
49
+ @resource.expects(:put).with({"foo" => "bar"}.to_json).when @resource.path.is("/some/path")
50
+ @client.put("/some/path", {"foo" => "bar"})
51
+ end
52
+
53
+ it "returns a Github::Client::Response" do
54
+ @resource.stubs(:put)
55
+ @client.put("/some/path", nil).should be_instance_of(Github::Client::Response)
56
+ end
57
+ end
58
+
59
+ describe "#delete" do
60
+ it "performs a DELETE request to /some/path on the resource" do
61
+ @resource.expects(:delete).when @resource.path.is("/some/path")
62
+ @client.delete("/some/path")
63
+ end
64
+
65
+ it "returns a Github::Client::Response" do
66
+ @resource.stubs(:delete)
67
+ @client.delete("/some/path").should be_instance_of(Github::Client::Response)
68
+ end
69
+ end
70
+
71
+ describe "#head" do
72
+ it "performs a HEAD request to /some/path on the resource" do
73
+ @resource.expects(:head).when @resource.path.is("/some/path")
74
+ @client.head("/some/path")
75
+ end
76
+
77
+ it "returns a Github::Client::Response" do
78
+ @resource.stubs(:head)
79
+ @client.head("/some/path").should be_instance_of(Github::Client::Response)
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ describe "Github::Client::Response" do
86
+
87
+ before :each do
88
+ @client = Github::Client.connect("http://localhost:#{mimic_port}")
89
+ end
90
+
91
+ def response
92
+ @client.get("/some/path")
93
+ end
94
+
95
+ def post_response(data = "")
96
+ @client.post("/some/path", data)
97
+ end
98
+
99
+ context "for a standard non-paginated 200 response" do
100
+ before :each do
101
+ Mimic.mimic.get("/some/path").returning({"result" => "ok"}.to_json, 200, {"X-RateLimit-Limit" => "5000", "X-RateLimit-Remaining" => "4966"})
102
+ end
103
+
104
+ it "indicates success" do
105
+ response.should be_success
106
+ end
107
+
108
+ it "stores the current rate limit" do
109
+ response.rate_limit.should == 5000
110
+ end
111
+
112
+ it "stores the number of rate-limited requests remaining" do
113
+ response.rate_limit_remaining.should == 4966
114
+ end
115
+
116
+ it "returns the parsed JSON response" do
117
+ response.data.should == {"result" => "ok"}
118
+ end
119
+ end
120
+
121
+ context "for 201 created responses" do
122
+ before :each do
123
+ Mimic.mimic.post("/some/path").returning("", 201, {"Location" => "http://example.com/resource"})
124
+ end
125
+
126
+ it "indicates success" do
127
+ post_response.should be_success
128
+ end
129
+
130
+ it "returns the location of the created resource" do
131
+ post_response.location.should == "http://example.com/resource"
132
+ end
133
+ end
134
+
135
+ context "for a bad request" do
136
+ before :each do
137
+ Mimic.mimic.get("/some/path").returning({"message" => "There was an error"}.to_json, 400, {})
138
+ end
139
+
140
+ it "indicates an error" do
141
+ response.should be_error
142
+ response.should_not be_success
143
+ end
144
+
145
+ it "returns the error message" do
146
+ response.error_message.should == "There was an error"
147
+ end
148
+ end
149
+
150
+ context "for requests with invalid data" do
151
+ before :each do
152
+ Mimic.mimic.get("/some/path").returning(
153
+ {"message" => "Validation failed",
154
+ "errors" => [
155
+ {"resource" => "Issue", "field" => "title", "code" => "missing_field"}
156
+ ]}.to_json, 422, {})
157
+ end
158
+
159
+ it "indicates an error" do
160
+ response.should be_error
161
+ response.should_not be_success
162
+ end
163
+
164
+ it "returns the error message" do
165
+ response.error_message.should == "Validation failed"
166
+ end
167
+
168
+ it "returns the errors" do
169
+ response.should have(1).errors
170
+ response.errors[0].resource.should == "Issue"
171
+ response.errors[0].field.should == "title"
172
+ response.errors[0].code.should == Github::Client::EntityError::MISSING_FIELD
173
+ end
174
+ end
175
+
176
+ context "for a server error" do
177
+ before :each do
178
+ Mimic.mimic.get("/some/path").returning("", 500)
179
+ end
180
+
181
+ it "raises an exception" do
182
+ proc{ @client.get("/some/path") }.should raise_error
183
+ end
184
+ end
185
+ end
186
+
187
+ describe "Github::Client authentication" do
188
+
189
+ context "with basic auth" do
190
+ before :each do
191
+ Mimic.mimic do
192
+ use Rack::Auth::Basic do |user, pass|
193
+ user == "joebloggs" and pass == "letmein"
194
+ end
195
+
196
+ get("/some/path") { [200, {}, ""] }
197
+ end
198
+ end
199
+
200
+ it "should succeed when authenticated correctly" do
201
+ @client = Github::Client.connect("http://localhost:#{mimic_port}", "joebloggs", "letmein")
202
+ @client.get("/some/path").should be_success
203
+ end
204
+
205
+ it "should fail when not authenticated correctly" do
206
+ @client = Github::Client.connect("http://localhost:#{mimic_port}", "joebloggs", "wrongpass")
207
+ @client.get("/some/path").should be_error
208
+ end
209
+ end
210
+
211
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+ require 'github/downloads'
3
+
4
+ describe "Github::Downloads" do
5
+
6
+ before :each do
7
+ @client = mock("client")
8
+ @downloads = Github::Downloads.new(@client, "username", "somerepo")
9
+ end
10
+
11
+ describe "#list" do
12
+ it "returns an empty array when there is no downloads" do
13
+ @client.stubs(:get).with("/repos/username/somerepo/downloads").returns successful_response_with([])
14
+ @downloads.list.should be_empty
15
+ end
16
+
17
+ it "returns an array of downloads parsed from the response when there are downloads" do
18
+ @client.stubs(:get).with("/repos/username/somerepo/downloads").returns successful_response_with([
19
+ {
20
+ "url" => "https://api.github.com/repos/octocat/Hello-World/downloads/1",
21
+ "html_url" => "https://github.com/repos/octocat/Hello-World/downloads/filename",
22
+ "id" => 1,
23
+ "name" => "file.zip",
24
+ "description" => "The latest release",
25
+ "size" => 1024,
26
+ "download_count" => 40
27
+ }
28
+ ])
29
+ @downloads.list.size.should == 1
30
+ @downloads.list.first.description.should == "The latest release"
31
+ end
32
+
33
+ it "raises if the response is not successful" do
34
+ @client.stubs(:get).with("/repos/username/somerepo/downloads").returns unsuccessful_response
35
+ proc { @downloads.list }.should raise_error(Github::Downloads::UnexpectedResponse)
36
+ end
37
+ end
38
+
39
+ describe "#upload" do
40
+
41
+ before :each do
42
+ @uploader = mock("uploader")
43
+ @downloads.uploader = @uploader
44
+ end
45
+
46
+ it "posts the file metadata to the server" do
47
+ @client.expects(:post).with("/repos/username/somerepo/downloads", {
48
+ :name => "textfile.txt",
49
+ :description => "an example file",
50
+ :content_type => "text/plain",
51
+ :size => File.size?("fixtures/textfile.txt")
52
+ }).returns(unsuccessful_response)
53
+
54
+ @downloads.create("fixtures/textfile.txt", "an example file")
55
+ end
56
+
57
+ it "passes the upload response data to the uploader if successful" do
58
+ @client.stubs(:post).returns successful_response_with("upload data")
59
+ @uploader.expects(:upload).with(File.expand_path("fixtures/textfile.txt"), "upload data")
60
+ @downloads.create("fixtures/textfile.txt", "an example file")
61
+ end
62
+
63
+ it "returns the URL returned by the uploader if successful" do
64
+ @client.stubs(:post).returns successful_response_with("upload data")
65
+ @uploader.stubs(:upload).returns("http://www.example.com/download")
66
+ @downloads.create("fixtures/textfile.txt", "an example file").should == "http://www.example.com/download"
67
+ end
68
+
69
+ it "returns false without uploading anything when upload post fails" do
70
+ @client.stubs(:post).returns unsuccessful_response
71
+ @uploader.expects(:upload).never
72
+ @downloads.create("fixtures/textfile.txt", "an example file").should be_false
73
+ end
74
+
75
+ end
76
+
77
+ private
78
+
79
+ def unsuccessful_response
80
+ stub("response", :success? => false)
81
+ end
82
+
83
+ def successful_response_with(data)
84
+ stub("response", :success? => true, :data => data)
85
+ end
86
+
87
+ end
@@ -0,0 +1,26 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require 'rspec'
4
+ require 'mocha'
5
+ require 'mimic'
6
+ require 'ap'
7
+
8
+ $:.unshift(File.join(File.dirname(__FILE__), *%w[.. lib]))
9
+
10
+ Rspec.configure do |config|
11
+ config.color_enabled = true
12
+ config.mock_with :mocha
13
+ config.before(:each) do
14
+ Mimic.cleanup!
15
+ end
16
+ end
17
+
18
+ USE_CHARLES_PROXY = false
19
+
20
+ def mimic_port
21
+ if USE_CHARLES_PROXY
22
+ 11989
23
+ else
24
+ Mimic::MIMIC_DEFAULT_PORT
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,229 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github-downloads
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Luke Redpath
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-27 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rest-client
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 9
28
+ segments:
29
+ - 1
30
+ - 6
31
+ - 3
32
+ version: 1.6.3
33
+ prerelease: false
34
+ type: :runtime
35
+ requirement: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: json
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 5
44
+ segments:
45
+ - 1
46
+ - 5
47
+ - 3
48
+ version: 1.5.3
49
+ prerelease: false
50
+ type: :runtime
51
+ requirement: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: simpleconsole
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 25
60
+ segments:
61
+ - 0
62
+ - 1
63
+ - 1
64
+ version: 0.1.1
65
+ prerelease: false
66
+ type: :runtime
67
+ requirement: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: hirb
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 5
76
+ segments:
77
+ - 0
78
+ - 4
79
+ - 5
80
+ version: 0.4.5
81
+ prerelease: false
82
+ type: :runtime
83
+ requirement: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ name: mime-types
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ hash: 87
92
+ segments:
93
+ - 1
94
+ - 16
95
+ - 0
96
+ version: 1.16.0
97
+ prerelease: false
98
+ type: :runtime
99
+ requirement: *id005
100
+ - !ruby/object:Gem::Dependency
101
+ name: highline
102
+ version_requirements: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ~>
106
+ - !ruby/object:Gem::Version
107
+ hash: 11
108
+ segments:
109
+ - 1
110
+ - 6
111
+ - 2
112
+ version: 1.6.2
113
+ prerelease: false
114
+ type: :runtime
115
+ requirement: *id006
116
+ - !ruby/object:Gem::Dependency
117
+ name: rspec
118
+ version_requirements: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ prerelease: false
128
+ type: :development
129
+ requirement: *id007
130
+ - !ruby/object:Gem::Dependency
131
+ name: mocha
132
+ version_requirements: &id008 !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ prerelease: false
142
+ type: :development
143
+ requirement: *id008
144
+ - !ruby/object:Gem::Dependency
145
+ name: mimic
146
+ version_requirements: &id009 !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ hash: 3
152
+ segments:
153
+ - 0
154
+ version: "0"
155
+ prerelease: false
156
+ type: :development
157
+ requirement: *id009
158
+ - !ruby/object:Gem::Dependency
159
+ name: awesome_print
160
+ version_requirements: &id010 !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 3
166
+ segments:
167
+ - 0
168
+ version: "0"
169
+ prerelease: false
170
+ type: :development
171
+ requirement: *id010
172
+ description:
173
+ email: luke@lukeredpath.co.uk
174
+ executables:
175
+ - github-downloads
176
+ extensions: []
177
+
178
+ extra_rdoc_files: []
179
+
180
+ files:
181
+ - Gemfile
182
+ - Gemfile.lock
183
+ - Rakefile
184
+ - README.md
185
+ - bin/github-downloads
186
+ - spec/fixtures/textfile.txt
187
+ - spec/github_client_spec.rb
188
+ - spec/github_download_spec.rb
189
+ - spec/spec_helper.rb
190
+ - lib/github/client.rb
191
+ - lib/github/downloads.rb
192
+ - lib/github/downloads_controller.rb
193
+ - lib/github/s3_uploader.rb
194
+ - lib/github.rb
195
+ homepage: http://lukeredpath.co.uk
196
+ licenses: []
197
+
198
+ post_install_message:
199
+ rdoc_options: []
200
+
201
+ require_paths:
202
+ - lib
203
+ required_ruby_version: !ruby/object:Gem::Requirement
204
+ none: false
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ hash: 3
209
+ segments:
210
+ - 0
211
+ version: "0"
212
+ required_rubygems_version: !ruby/object:Gem::Requirement
213
+ none: false
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ hash: 3
218
+ segments:
219
+ - 0
220
+ version: "0"
221
+ requirements: []
222
+
223
+ rubyforge_project:
224
+ rubygems_version: 1.8.5
225
+ signing_key:
226
+ specification_version: 3
227
+ summary: Manages downloads for your Github projects
228
+ test_files: []
229
+