codefumes 0.1.0

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.
@@ -0,0 +1,113 @@
1
+ module CodeFumes
2
+ # A Project encapsulates the concept of a project on the CodeFumes.com
3
+ # website. Each project has a public key, private key, and can have a
4
+ # name defined. Projects are also associated with a collection of
5
+ # commits from a repository.
6
+ class Project < CodeFumes::API
7
+ attr_reader :private_key, :short_uri, :community_uri, :api_uri
8
+ attr_accessor :name,:public_key
9
+
10
+ # Accepts Hash containing the following keys:
11
+ # * :public_key
12
+ # * :private_key
13
+ # * :name
14
+ def initialize(options = {})
15
+ @public_key = options[:public_key]
16
+ @private_key = options[:private_key]
17
+ @name = options[:name]
18
+ end
19
+
20
+ # Deletes project from the website.
21
+ #
22
+ # Returns +true+ if the request succeeded.
23
+ #
24
+ # Returns +false+ if the request failed.
25
+ def delete
26
+ response = destroy!
27
+ case response.code
28
+ when 200
29
+ return true
30
+ else
31
+ return false
32
+ end
33
+ end
34
+
35
+ # Saves project +:public_key+ to the website. If the public key
36
+ # of the project has not been reserved yet, it will attempt to do
37
+ # so. If the public key of the project is already in use, it will
38
+ # attempt to update it with the current values.
39
+ #
40
+ # Returns +true+ if the request succeeded.
41
+ #
42
+ # Returns +false+ if the request failed.
43
+ def save
44
+ response = exists? ? update : create
45
+ case response.code
46
+ when 201, 200
47
+ reinitialize!(response)
48
+ true
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ # Serializes a Project instance to a format compatible with the
55
+ # CodeFumes config file.
56
+ def to_config
57
+ project_attributes = {:api_uri => @api_uri, :short_uri => @short_uri}
58
+ project_attributes[:private_key] = @private_key if @private_key
59
+ {@public_key.to_sym => project_attributes}
60
+ end
61
+
62
+ # Verifies existence of Project on website.
63
+ #
64
+ # Returns +true+ if the public key of Project is available.
65
+ #
66
+ # Returns +false+ if the public key of the Project is not available.
67
+ def exists?
68
+ return false if @public_key.nil? || @public_key.empty?
69
+ !self.class.find(@public_key).nil?
70
+ end
71
+
72
+
73
+ # Searches website for project with the supplied public key.
74
+ #
75
+ # Returns a Project instance if the project exists and is available,
76
+ # to the user making the request.
77
+ #
78
+ # Returns +nil+ in all other cases.
79
+ def self.find(public_key)
80
+ response = get("/projects/#{public_key}")
81
+ case response.code
82
+ when 200
83
+ project = Project.new
84
+ project.reinitialize!(response)
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ # TODO: Make this a private method
91
+ def reinitialize!(options = {}) #:nodoc:
92
+ @public_key = options['project']['public_key']
93
+ @private_key = options['project']['private_key']
94
+ @short_uri = options['project']['short_uri']
95
+ @community_uri = options['project']['community_uri']
96
+ @api_uri = options['project']['api_uri']
97
+ self
98
+ end
99
+
100
+ private
101
+ def update
102
+ self.class.put("/projects/#{@public_key}", :query => {:project => {:name => @name}}, :basic_auth => {:username => @public_key, :password => @private_key})
103
+ end
104
+
105
+ def create
106
+ self.class.post('/projects', :query => {:project => {:name => @name, :public_key => @public_key}})
107
+ end
108
+
109
+ def destroy!
110
+ self.class.delete("/projects/#{@public_key}", :basic_auth => {:username => @public_key, :password => @private_key})
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ class APIClass < CodeFumes::API
4
+ end
5
+
6
+ describe "API" do
7
+
8
+ after(:all) do
9
+ CodeFumes::API.mode(:production)
10
+ end
11
+
12
+ it "defaults the base uri to the production site" do
13
+ APIClass.base_uri.should == 'http://www.codefumes.com/api/v1/xml'
14
+ end
15
+
16
+ context "switching modes" do
17
+ before(:each) do
18
+ CodeFumes::API.mode(:test)
19
+ end
20
+ it "changes the base uri to the test site when switched to test mode" do
21
+ APIClass.base_uri.should == 'http://test.codefumes.com/api/v1/xml'
22
+ end
23
+ it "changes the base uri to the production site when switched to production mode" do
24
+ CodeFumes::API.mode(:production)
25
+ APIClass.base_uri.should == 'http://www.codefumes.com/api/v1/xml'
26
+ end
27
+ it "ignores unrecognized modes" do
28
+ CodeFumes::API.mode(:incomprehensible)
29
+ APIClass.base_uri.should == 'http://test.codefumes.com/api/v1/xml'
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,270 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ def single_commit(options = {})
4
+ commit_xml = <<-END_OF_COMMIT
5
+ <commit>
6
+ <identifier>f3badd5624dfbcf5176f0471261731e1b92ce957</identifier>
7
+ <author_name>John Doe</author_name>
8
+ <author_email>jdoe@example.com</author_email>
9
+ <committer_name>John Doe</committer_name>
10
+ <committer_email>jdoe@example.com</committer_email>
11
+ <short_message>Made command-line option for 'name' actually work</short_message>
12
+ <message>
13
+ Made command-line option for 'name' actually work
14
+ - Commentd out hard-coded 'require' line used for testing
15
+ </message>
16
+ <parent_identifiers>9ddj48423jdsjds5176f0471261731e1b92ce957,3ewdjok23jdsjds5176f0471261731e1b92ce957,284djsksjfjsjds5176f0471261731e1b92ce957</parent_identifiers>
17
+ <committed_at>Wed May 20 09:09:06 -0500 2009</committed_at>
18
+ <authored_at>Wed May 20 09:09:06 -0500 2009</authored_at>
19
+ <uploaded_at>2009-06-04 02:43:20 UTC</uploaded_at>
20
+ <api_uri>http://localhost:3000/api/v1/commits/f3badd5624dfbcf5176f0471261731e1b92ce957.xml</api_uri>
21
+ <line_additions>20</line_additions>
22
+ <line_deletions>10</line_deletions>
23
+ <line_total>30</line_total>
24
+ <affected_file_count>2</affected_file_count>
25
+ END_OF_COMMIT
26
+
27
+ if options[:include_custom_attributes]
28
+ commit_xml <<
29
+ <<-END_OF_COMMIT
30
+ <custom_attributes>
31
+ <coverage>83</coverage>
32
+ <random_attribute>1</random_attribute>
33
+ </custom_attributes>
34
+ END_OF_COMMIT
35
+ end
36
+
37
+ commit_xml << "\n</commit>"
38
+ end
39
+
40
+
41
+ def register_index_uri
42
+ FakeWeb.register_uri(
43
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/apk/commits",
44
+ :status => ["200", "Ok"],
45
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<commits>\n#{single_commit}\n#{single_commit}\n#{single_commit}\n</commits>\n")
46
+ end
47
+
48
+ describe "Commit" do
49
+ before(:all) do
50
+ @identifier = "f3badd5624dfbcf5176f0471261731e1b92ce957"
51
+ FakeWeb.allow_net_connect = false
52
+ end
53
+
54
+ after(:all) do
55
+ FakeWeb.clean_registry
56
+ end
57
+
58
+ describe "find" do
59
+ context "with a valid commit identifier" do
60
+ before(:each) do
61
+ FakeWeb.register_uri(
62
+ :get, "http://www.codefumes.com:80/api/v1/xml/commits/f3badd5624dfbcf5176f0471261731e1b92ce957",
63
+ :status => ["200", "Ok"],
64
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
65
+ @commit = Commit.find(@identifier)
66
+ end
67
+
68
+ [:identifier,
69
+ :author_email,
70
+ :author_name,
71
+ :committer_name,
72
+ :committer_email,
73
+ :short_message,
74
+ :message,
75
+ :committed_at,
76
+ :authored_at,
77
+ :uploaded_at,
78
+ :api_uri,
79
+ :parent_identifiers,
80
+ :line_additions,
81
+ :line_deletions,
82
+ :line_total,
83
+ :affected_file_count,
84
+ ].each do |method_name|
85
+ it "sets the '#{method_name.to_s}'" do
86
+ @commit.send(method_name).should_not == nil
87
+ end
88
+ end
89
+ end
90
+
91
+ context "with a non-existant commit identifier" do
92
+ before(:each) do
93
+ @identifier = "non_existant_commit_identifier"
94
+ FakeWeb.register_uri( :get, "http://www.codefumes.com:80/api/v1/xml/commits/#{@identifier}",
95
+ :status => ["404", "Not Found"])
96
+ end
97
+
98
+ it "returns nil" do
99
+ Commit.find(@identifier).should == nil
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "calling 'latest'" do
105
+ before(:each) do
106
+ @project_public_key = "apk"
107
+ end
108
+
109
+ context "with valid parameters" do
110
+ before(:each) do
111
+ FakeWeb.register_uri(
112
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
113
+ :status => ["200", "Ok"],
114
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
115
+ end
116
+
117
+ it "returns a commit object for the latest commit" do
118
+ Commit.latest(@project_public_key).identifier.should == @identifier
119
+ end
120
+ end
121
+
122
+ context "with invalid parameters" do
123
+ before(:each) do
124
+ FakeWeb.register_uri(
125
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
126
+ :status => ["404", "Not Found"],
127
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
128
+ end
129
+
130
+ it "returns nil" do
131
+ Commit.latest(@project_public_key).should == nil
132
+ end
133
+ end
134
+ end
135
+
136
+ describe "calling 'latest_identifier'" do
137
+ before(:each) do
138
+ @project_public_key = "apk"
139
+ end
140
+
141
+ context "with valid parameters" do
142
+ context "when the specified project has commits stored" do
143
+ before(:each) do
144
+ FakeWeb.register_uri(
145
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
146
+ :status => ["200", "Ok"],
147
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
148
+ end
149
+
150
+ it "returns the commit identifier of the latest commit" do
151
+ Commit.latest_identifier(@project_public_key).should == @identifier
152
+ end
153
+ end
154
+
155
+ context "when the specified project does not have any commits stored" do
156
+ before(:each) do
157
+ FakeWeb.register_uri(
158
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
159
+ :status => ["404", "Not Found"],
160
+ :string => "")
161
+ end
162
+
163
+ it "returns nil" do
164
+ Commit.latest_identifier(@project_public_key).should == nil
165
+ end
166
+ end
167
+ end
168
+
169
+ context "with invalid parameters" do
170
+ before(:each) do
171
+ FakeWeb.register_uri(
172
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
173
+ :status => ["404", "Not Found"],
174
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
175
+ end
176
+
177
+ it "returns nil" do
178
+ Commit.latest(@project_public_key).should == nil
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "calling 'all'" do
184
+ before(:each) do
185
+ register_index_uri
186
+ @project_public_key = "apk"
187
+ end
188
+
189
+ context "with valid parameters" do
190
+ it "returns an array of commits" do
191
+ Commit.all(@project_public_key).should have(3).items
192
+ end
193
+ end
194
+
195
+ context "with invalid parameters" do
196
+ before(:each) do
197
+ FakeWeb.register_uri(
198
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/apk/commits",
199
+ :status => ["404", "Not Found"],
200
+ :string => "")
201
+ end
202
+
203
+ it "returns nil" do
204
+ Commit.all(@project_public_key).should == nil
205
+ end
206
+ end
207
+ end
208
+
209
+ describe "the convenience method" do
210
+ before(:each) do
211
+ @email = "jdoe@example.com"
212
+ @name = "John Doe"
213
+ FakeWeb.register_uri(
214
+ :get, "http://www.codefumes.com:80/api/v1/xml/commits/f3badd5624dfbcf5176f0471261731e1b92ce957",
215
+ :status => ["200", "Ok"],
216
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
217
+ @commit = Commit.find(@identifier)
218
+ end
219
+
220
+ describe "author" do
221
+ it "returns a concatenated string containing the author's name & email" do
222
+ @commit.author.should =~ /#{@name}/
223
+ @commit.author.should =~ /#{@email}/
224
+ end
225
+ end
226
+
227
+ describe "committer" do
228
+ it "returns a concatenated string containing the author's name & email" do
229
+ @commit.committer.should =~ /#{@name}/
230
+ @commit.committer.should =~ /#{@email}/
231
+ end
232
+ end
233
+ end
234
+
235
+ describe "accessing custom metrics" do
236
+ before(:each) do
237
+ @project_public_key = "apk"
238
+ end
239
+
240
+ context "when the commit does not have any custom attributes" do
241
+ before(:each) do
242
+ FakeWeb.register_uri(
243
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
244
+ :status => ["200", "Ok"],
245
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{single_commit}")
246
+ end
247
+
248
+ it "returns an empty Hash" do
249
+ Commit.latest(@project_public_key).custom_attributes.should == {}
250
+ end
251
+ end
252
+
253
+ context "when the commit has defined custom attributes" do
254
+ before(:each) do
255
+ commit_content = single_commit(:include_custom_attributes => true)
256
+ FakeWeb.register_uri(
257
+ :get, "http://www.codefumes.com:80/api/v1/xml/projects/#{@project_public_key}/commits/latest",
258
+ :status => ["200", "Ok"],
259
+ :string => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{commit_content}")
260
+ end
261
+
262
+ it "returns a Hash of key-value pairs (attribute_name -> attribute_value)" do
263
+ Commit.latest(@project_public_key).custom_attributes.should be_instance_of(Hash)
264
+ Commit.latest(@project_public_key).custom_attributes[:coverage].should == "83"
265
+ Commit.latest(@project_public_key).custom_attributes[:random_attribute].should == "1"
266
+ Commit.latest(@project_public_key).custom_attributes.size.should == 2
267
+ end
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe "ConfigFile" do
4
+ before(:each) do
5
+ @project = Project.new(:public_key => 'public_key_value', :private_key => 'private_key_value')
6
+ end
7
+
8
+ after(:all) do
9
+ unless ConfigFile.path == File.expand_path('~/.codefumes_config')
10
+ File.delete(ConfigFile.path) if File.exist?(ConfigFile.path)
11
+ end
12
+ end
13
+
14
+ describe "calling 'path'" do
15
+ before(:each) do
16
+ @original_path = ENV['CODEFUMES_CONFIG_FILE']
17
+ end
18
+
19
+ after(:all) do
20
+ ENV['CODEFUMES_CONFIG_FILE'] = @original_path
21
+ end
22
+
23
+ it "returns a default value of the full path to a dotfile named '.codefumes_config' in the user's home directory" do
24
+ ENV['CODEFUMES_CONFIG_FILE'] = nil
25
+ ConfigFile.path.should == File.expand_path('~/.codefumes_config')
26
+ end
27
+ end
28
+
29
+ describe "calling 'save_project'" do
30
+ after(:all) do
31
+ File.delete(ConfigFile.path) if File.exist?(ConfigFile.path)
32
+ end
33
+
34
+ context "when passed a new project" do
35
+ it "creates the config file if it did not exist already" do
36
+ File.exist?(ConfigFile.path).should be_false
37
+ ConfigFile.save_project(@project)
38
+ File.exist?(ConfigFile.path).should be_true
39
+ end
40
+
41
+ it "adds the supplied project's public key as a new entry under 'projects'" do
42
+ ConfigFile.save_project(@project)
43
+ ConfigFile.serialized[:projects].should include(@project.to_config)
44
+ end
45
+ end
46
+
47
+ context "when passed an existing project" do
48
+ before(:each) do
49
+ ConfigFile.save_project(@project)
50
+ @updated_project = Project.new(:public_key => @project.public_key, :private_key => "updated_private_key")
51
+ end
52
+
53
+ it "does not create a duplicate entry in the list of projects" do
54
+ ConfigFile.serialized[:projects].should include(@project.to_config)
55
+ ConfigFile.serialized[:projects].count.should == 1
56
+ ConfigFile.save_project(@updated_project)
57
+ ConfigFile.serialized[:projects].should include(@updated_project.to_config)
58
+ ConfigFile.serialized.count.should == 1
59
+ end
60
+ end
61
+
62
+ context "when several projects exist" do
63
+ before(:each) do
64
+ @project1 = Project.new(:public_key => :p1_pub_value, :private_key => "p1_private_key")
65
+ @project2 = Project.new(:public_key => :p2_pub_value, :private_key => "p2_private_key")
66
+ @project3 = Project.new(:public_key => :p3_pub_value, :private_key => "p3_private_key")
67
+ ConfigFile.save_project(@project1)
68
+ ConfigFile.save_project(@project2)
69
+ ConfigFile.save_project(@project3)
70
+ end
71
+
72
+ it "does not modify data pertaining to other projects" do
73
+ ConfigFile.serialized[:projects].should include(@project1.to_config)
74
+ ConfigFile.serialized[:projects].should include(@project2.to_config)
75
+ ConfigFile.serialized[:projects].should include(@project3.to_config)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "calling 'delete_project'" do
81
+ context "when the project entry exists in the file" do
82
+ before(:each) do
83
+ @project1 = Project.new(:public_key => "p1_pub_value", :private_key => "p1_private_key")
84
+ @project2 = Project.new(:public_key => "p2_pub_value", :private_key => "p2_private_key")
85
+ @project3 = Project.new(:public_key => "p3_pub_value", :private_key => "p3_private_key")
86
+ ConfigFile.save_project(@project1)
87
+ ConfigFile.save_project(@project2)
88
+ ConfigFile.save_project(@project3)
89
+ ConfigFile.delete_project(@project2)
90
+ end
91
+
92
+ it "removes the entry for the supplied project from the file" do
93
+ ConfigFile.serialized[:projects].should_not include(@project2.to_config)
94
+ end
95
+
96
+ xit "does not affect other project entries" do
97
+ ConfigFile.serialized[:projects].should include(@project1.to_config)
98
+ ConfigFile.serialized[:projects].should include(@project3.to_config)
99
+ end
100
+ end
101
+
102
+ context "when the project entry does not exist in the file" do
103
+ before(:each) do
104
+ @project = Project.new(:public_key => "p1_pub_nonexist_value", :private_key => "p1_private_key")
105
+ end
106
+
107
+ it "does not raise an error" do
108
+ lambda {ConfigFile.delete_project(@project)}.should_not raise_error
109
+ end
110
+ end
111
+ end
112
+
113
+ describe 'setting a custom path' do
114
+ before(:each) do
115
+ @original_path = ENV['CODEFUMES_CONFIG_FILE']
116
+ ENV['CODEFUMES_CONFIG_FILE'] = nil
117
+ end
118
+
119
+ after(:all) do
120
+ ENV['CODEFUMES_CONFIG_FILE'] = @original_path
121
+ end
122
+
123
+ context "via an environment variable" do
124
+ it "updates the value returned from 'path'" do
125
+ new_path = File.expand_path('./tmp/new_config_via_env_var')
126
+ ConfigFile.path.should == File.expand_path('~/.codefumes_config')
127
+ ENV['CODEFUMES_CONFIG_FILE'] = new_path
128
+ ConfigFile.path.should == new_path
129
+ end
130
+ end
131
+
132
+ context "via path=" do
133
+ it "updates the value returned from 'path'" do
134
+ new_path = File.expand_path('./tmp/new_config')
135
+ ConfigFile.path.should == File.expand_path('~/.codefumes_config')
136
+ ConfigFile.path = new_path
137
+ ConfigFile.path.should == new_path
138
+ end
139
+ end
140
+ end
141
+
142
+ end