codefumes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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