turingstudio-basecamp-rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-02-18
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/README.rdoc ADDED
@@ -0,0 +1,89 @@
1
+ = basecamp-rb
2
+
3
+ http://github.com/turingstudio/basecamp-rb/
4
+
5
+ == DESCRIPTION:
6
+
7
+ A Ruby gem for working with the Basecamp web-services API.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+
12
+ == SYNOPSIS:
13
+
14
+ The first thing you need to do is establish a connection to Basecamp. This
15
+ requires your Basecamp site address and your login credentials. Example:
16
+ Basecamp::Base.establish_connection!('your.grouphub.com', 'login', 'password')
17
+
18
+ m = Basecamp::Message.find(8675309)
19
+ m.title # => 'Jenny'
20
+
21
+ >> Creating a Resource
22
+
23
+ m = Basecamp::Message.new(:project_id => 1037)
24
+ m.category_id = 7301
25
+ m.title = 'Message in a bottle'
26
+ m.body = 'Another lonely day, with no one here but me'
27
+ m.save # => true
28
+
29
+ >> Updating a Resource
30
+
31
+ m = Basecamp::Message.find(8675309)
32
+ m.body = 'Changed'
33
+ m.save # => true
34
+
35
+ >> Deleting a Resource
36
+
37
+ Basecamp::Message.delete(1037)
38
+
39
+ >> Using the non-REST inteface
40
+
41
+ # The non-REST interface is accessed via instance methods on the Basecamp
42
+ # class. Ensure you've established a connection, then create a new Basecamp
43
+ # instance and call methods on it. Examples:
44
+
45
+ basecamp = Basecamp::Base.new
46
+
47
+ basecamp.projects.length # => 5
48
+ basecamp.person(93832) # => #<Record(person)..>
49
+ basecamp.file_categories(123) # => [#<Record(file-category)>,#<Record..>]
50
+
51
+ # Object attributes are accessible as methods. Example:
52
+
53
+ person = basecamp.person(93832)
54
+ person.first_name # => "Jason"
55
+
56
+
57
+ == REQUIREMENTS:
58
+
59
+ xml-simple >= 1.0.11
60
+ activeresource >= 2.2.2
61
+
62
+ == INSTALL:
63
+
64
+ gem install turingstudio-basecamp-rb
65
+
66
+ == LICENSE:
67
+
68
+ (The MIT License)
69
+
70
+ Copyright (c) 2008 The Turing Studio, Inc.
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining
73
+ a copy of this software and associated documentation files (the
74
+ 'Software'), to deal in the Software without restriction, including
75
+ without limitation the rights to use, copy, modify, merge, publish,
76
+ distribute, sublicense, and/or sell copies of the Software, and to
77
+ permit persons to whom the Software is furnished to do so, subject to
78
+ the following conditions:
79
+
80
+ The above copyright notice and this permission notice shall be
81
+ included in all copies or substantial portions of the Software.
82
+
83
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
84
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
85
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
86
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
87
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
88
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
89
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/basecamp.rb ADDED
@@ -0,0 +1,31 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'yaml'
4
+ require 'date'
5
+ require 'time'
6
+ require 'xmlsimple'
7
+ require 'activeresource'
8
+
9
+ require 'basecamp/base'
10
+ require 'basecamp/resource'
11
+ require 'basecamp/attachment'
12
+ require 'basecamp/comment'
13
+ require 'basecamp/connection'
14
+ require 'basecamp/message'
15
+ require 'basecamp/record'
16
+ require 'basecamp/time_entry'
17
+ require 'basecamp/todoitem'
18
+ require 'basecamp/todolist'
19
+
20
+ # A minor hack to let Xml-Simple serialize symbolic keys in hashes
21
+ class Symbol
22
+ def [](*args)
23
+ to_s[*args]
24
+ end
25
+ end
26
+
27
+ class Hash
28
+ def to_legacy_xml
29
+ XmlSimple.xml_out({:request => self}, 'keeproot' => true, 'noattr' => true)
30
+ end
31
+ end
@@ -0,0 +1,53 @@
1
+ module Basecamp
2
+ class Attachment
3
+
4
+ # === Attaching Files to a Resource
5
+ #
6
+ # If the resource accepts file attachments, the +attachments+ parameter should
7
+ # be an array of Basecamp::Attachment objects. Example:
8
+ #
9
+ # a1 = Basecamp::Atachment.create('primary', File.read('primary.doc'))
10
+ # a2 = Basecamp::Atachment.create('another', File.read('another.doc'))
11
+ #
12
+ # m = Basecamp::Message.new(:project_id => 1037)
13
+ # ...
14
+ # m.attachments = [a1, a2]
15
+ # m.save # => true
16
+ #
17
+
18
+ attr_accessor :id, :filename, :content, :content_type
19
+
20
+ def self.create(filename, content)
21
+ returning new(filename, content) do |attachment|
22
+ attachment.save
23
+ end
24
+ end
25
+
26
+ def initialize(filename, content, content_type = 'application/octet-stream')
27
+ @filename, @content, @content_type = filename, content, content_type
28
+ end
29
+
30
+ def attributes
31
+ { :file => id, :original_filename => filename, :content_type => content_type }
32
+ end
33
+
34
+ def to_xml(options = {})
35
+ { :file => attributes }.to_xml(options)
36
+ end
37
+
38
+ def inspect
39
+ to_s
40
+ end
41
+
42
+ def save
43
+ response = Basecamp::Base.connection.post('/upload', content, 'Content-Type' => content_type)
44
+
45
+ if response.code == '200'
46
+ self.id = Hash.from_xml(response.body)['upload']['id']
47
+ true
48
+ else
49
+ raise "Could not save attachment: #{response.message} (#{response.code})"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,210 @@
1
+ module Basecamp
2
+ class Base
3
+ attr_accessor :use_xml
4
+
5
+ class << self
6
+ attr_reader :site, :user, :password, :use_ssl
7
+
8
+ def establish_connection!(site, user, password, use_ssl = false)
9
+ @site = site
10
+ @user = user
11
+ @password = password
12
+ @use_ssl = use_ssl
13
+ Resource.user = user
14
+ Resource.password = password
15
+ Resource.site = (use_ssl ? "https" : "http") + "://" + site
16
+ @connection = Connection.new(self)
17
+ end
18
+
19
+ def connection
20
+ @connection || raise('No connection established')
21
+ end
22
+
23
+ end
24
+
25
+ def initialize
26
+ @use_xml = false
27
+ end
28
+
29
+ # ==========================================================================
30
+ # GENERAL
31
+ # ==========================================================================
32
+
33
+ # Return the list of all accessible projects
34
+ def projects
35
+ records "project", "/project/list"
36
+ end
37
+
38
+ # Returns the list of message categories for the given project
39
+ def message_categories(project_id)
40
+ records "post-category", "/projects/#{project_id}/post_categories"
41
+ end
42
+
43
+ # Returns the list of file categories for the given project
44
+ def file_categories(project_id)
45
+ records "attachment-category", "/projects/#{project_id}/attachment_categories"
46
+ end
47
+
48
+ # ==========================================================================
49
+ # CONTACT MANAGEMENT
50
+ # ==========================================================================
51
+
52
+ # Return information for the company with the given id
53
+ def company(id)
54
+ record "/contacts/company/#{id}"
55
+ end
56
+
57
+ # Return an array of the people in the given company. If the project-id is
58
+ # given, only people who have access to the given project will be returned.
59
+ def people(company_id, project_id=nil)
60
+ url = project_id ? "/projects/#{project_id}" : ""
61
+ url << "/contacts/people/#{company_id}"
62
+ records "person", url
63
+ end
64
+
65
+ # Return information about the person with the given id
66
+ def person(id)
67
+ record "/contacts/person/#{id}"
68
+ end
69
+
70
+ # ==========================================================================
71
+ # MILESTONES
72
+ # ==========================================================================
73
+
74
+ # Complete the milestone with the given id
75
+ def complete_milestone(id)
76
+ record "/milestones/complete/#{id}"
77
+ end
78
+
79
+ # Create a new milestone for the given project. +data+ must be hash of the
80
+ # values to set, including +title+, +deadline+, +responsible_party+, and
81
+ # +notify+.
82
+ def create_milestone(project_id, data)
83
+ create_milestones(project_id, [data]).first
84
+ end
85
+
86
+ # As #create_milestone, but can create multiple milestones in a single
87
+ # request. The +milestones+ parameter must be an array of milestone values as
88
+ # descrbed in #create_milestone.
89
+ def create_milestones(project_id, milestones)
90
+ records "milestone", "/projects/#{project_id}/milestones/create", :milestone => milestones
91
+ end
92
+
93
+ # Destroys the milestone with the given id.
94
+ def delete_milestone(id)
95
+ record "/milestones/delete/#{id}"
96
+ end
97
+
98
+ # Returns a list of all milestones for the given project, optionally filtered
99
+ # by whether they are completed, late, or upcoming.
100
+ def milestones(project_id, find="all")
101
+ records "milestone", "/projects/#{project_id}/milestones/list", :find => find
102
+ end
103
+
104
+ # Uncomplete the milestone with the given id
105
+ def uncomplete_milestone(id)
106
+ record "/milestones/uncomplete/#{id}"
107
+ end
108
+
109
+ # Updates an existing milestone.
110
+ def update_milestone(id, data, move=false, move_off_weekends=false)
111
+ record "/milestones/update/#{id}", :milestone => data,
112
+ :move_upcoming_milestones => move,
113
+ :move_upcoming_milestones_off_weekends => move_off_weekends
114
+ end
115
+
116
+ private
117
+
118
+ # Make a raw web-service request to Basecamp. This will return a Hash of
119
+ # Arrays of the response, and may seem a little odd to the uninitiated.
120
+ def request(path, parameters = {})
121
+ response = Base.connection.post(path, convert_body(parameters), "Content-Type" => content_type)
122
+
123
+ if response.code.to_i / 100 == 2
124
+ result = XmlSimple.xml_in(response.body, 'keeproot' => true, 'contentkey' => '__content__', 'forcecontent' => true)
125
+ typecast_value(result)
126
+ else
127
+ raise "#{response.message} (#{response.code})"
128
+ end
129
+ end
130
+
131
+ # A convenience method for wrapping the result of a query in a Record
132
+ # object. This assumes that the result is a singleton, not a collection.
133
+ def record(path, parameters={})
134
+ result = request(path, parameters)
135
+ (result && !result.empty?) ? Record.new(result.keys.first, result.values.first) : nil
136
+ end
137
+
138
+ # A convenience method for wrapping the result of a query in Record
139
+ # objects. This assumes that the result is a collection--any singleton
140
+ # result will be wrapped in an array.
141
+ def records(node, path, parameters={})
142
+ result = request(path, parameters).values.first or return []
143
+ result = result[node] or return []
144
+ result = [result] unless Array === result
145
+ result.map { |row| Record.new(node, row) }
146
+ end
147
+
148
+ def convert_body(body)
149
+ body = use_xml ? body.to_legacy_xml : body.to_yaml
150
+ end
151
+
152
+ def content_type
153
+ use_xml ? "application/xml" : "application/x-yaml"
154
+ end
155
+
156
+ def typecast_value(value)
157
+ case value
158
+ when Hash
159
+ if value.has_key?("__content__")
160
+ content = translate_entities(value["__content__"]).strip
161
+ case value["type"]
162
+ when "integer" then content.to_i
163
+ when "boolean" then content == "true"
164
+ when "datetime" then Time.parse(content)
165
+ when "date" then Date.parse(content)
166
+ else content
167
+ end
168
+ # a special case to work-around a bug in XmlSimple. When you have an empty
169
+ # tag that has an attribute, XmlSimple will not add the __content__ key
170
+ # to the returned hash. Thus, we check for the presense of the 'type'
171
+ # attribute to look for empty, typed tags, and simply return nil for
172
+ # their value.
173
+ elsif value.keys == %w(type)
174
+ nil
175
+ elsif value["nil"] == "true"
176
+ nil
177
+ # another special case, introduced by the latest rails, where an array
178
+ # type now exists. This is parsed by XmlSimple as a two-key hash, where
179
+ # one key is 'type' and the other is the actual array value.
180
+ elsif value.keys.length == 2 && value["type"] == "array"
181
+ value.delete("type")
182
+ typecast_value(value)
183
+ else
184
+ value.empty? ? nil : value.inject({}) do |h,(k,v)|
185
+ h[k] = typecast_value(v)
186
+ h
187
+ end
188
+ end
189
+ when Array
190
+ value.map! { |i| typecast_value(i) }
191
+ case value.length
192
+ when 0 then nil
193
+ when 1 then value.first
194
+ else value
195
+ end
196
+ else
197
+ raise "can't typecast #{value.inspect}"
198
+ end
199
+ end
200
+
201
+ def translate_entities(value)
202
+ value.gsub(/&lt;/, "<").
203
+ gsub(/&gt;/, ">").
204
+ gsub(/&quot;/, '"').
205
+ gsub(/&apos;/, "'").
206
+ gsub(/&amp;/, "&")
207
+ end
208
+ end
209
+ end
210
+
@@ -0,0 +1,23 @@
1
+ # == Creating Comments for Multiple Resources
2
+ #
3
+ # Comments can be created for messages, milestones, and to-dos, identified
4
+ # by the <tt>post_id</tt>, <tt>milestone_id</tt>, and <tt>todo_item_id</tt>
5
+ # params respectively.
6
+ #
7
+ # For example, to create a comment on the message with id #8675309:
8
+ #
9
+ # c = Basecamp::Comment.new(:post_id => 8675309)
10
+ # c.body = 'Great tune'
11
+ # c.save # => true
12
+ #
13
+ # Similarly, to create a comment on a milestone:
14
+ #
15
+ # c = Basecamp::Comment.new(:milestone_id => 8473647)
16
+ # c.body = 'Is this done yet?'
17
+ # c.save # => true
18
+ #
19
+ module Basecamp
20
+ class Comment < Resource
21
+ parent_resources :post, :milestone, :todo_item
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ module Basecamp
2
+ class Connection #:nodoc:
3
+ def initialize(master)
4
+ @master = master
5
+ @connection = Net::HTTP.new(master.site, master.use_ssl ? 443 : 80)
6
+ @connection.use_ssl = master.use_ssl
7
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if master.use_ssl
8
+ end
9
+
10
+ def post(path, body, headers = {})
11
+ request = Net::HTTP::Post.new(path, headers.merge('Accept' => 'application/xml'))
12
+ request.basic_auth(@master.user, @master.password)
13
+ @connection.request(request, body)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ module Basecamp
2
+ class Message < Resource
3
+ parent_resources :project
4
+ self.element_name = 'post'
5
+
6
+ # Returns the most recent 25 messages in the given project (and category,
7
+ # if specified). If you need to retrieve older messages, use the archive
8
+ # method instead. Example:
9
+ #
10
+ # Basecamp::Message.list(1037)
11
+ # Basecamp::Message.list(1037, :category_id => 7301)
12
+ #
13
+ def self.list(project_id, options = {})
14
+ find(:all, :params => options.merge(:project_id => project_id))
15
+ end
16
+
17
+ # Returns a summary of all messages in the given project (and category, if
18
+ # specified). The summary is simply the title and category of the message,
19
+ # as well as the number of attachments (if any). Example:
20
+ #
21
+ # Basecamp::Message.archive(1037)
22
+ # Basecamp::Message.archive(1037, :category_id => 7301)
23
+ #
24
+ def self.archive(project_id, options = {})
25
+ find(:all, :params => options.merge(:project_id => project_id), :from => :archive)
26
+ end
27
+
28
+ def comments(options = {})
29
+ @comments ||= Comment.find(:all, :params => options.merge(:post_id => id))
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ module Basecamp
2
+ class Record #:nodoc:
3
+ attr_reader :type
4
+
5
+ def initialize(type, hash)
6
+ @type, @hash = type, hash
7
+ end
8
+
9
+ def [](name)
10
+ name = dashify(name)
11
+
12
+ case @hash[name]
13
+ when Hash then
14
+ @hash[name] = if (@hash[name].keys.length == 1 && @hash[name].values.first.is_a?(Array))
15
+ @hash[name].values.first.map { |v| Record.new(@hash[name].keys.first, v) }
16
+ else
17
+ Record.new(name, @hash[name])
18
+ end
19
+ else
20
+ @hash[name]
21
+ end
22
+ end
23
+
24
+ def id
25
+ @hash['id']
26
+ end
27
+
28
+ def attributes
29
+ @hash.keys
30
+ end
31
+
32
+ def respond_to?(sym)
33
+ super || @hash.has_key?(dashify(sym))
34
+ end
35
+
36
+ def method_missing(sym, *args)
37
+ if args.empty? && !block_given? && respond_to?(sym)
38
+ self[sym]
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def to_s
45
+ "\#<Record(#{@type}) #{@hash.inspect[1..-2]}>"
46
+ end
47
+
48
+ def inspect
49
+ to_s
50
+ end
51
+
52
+ private
53
+
54
+ def dashify(name)
55
+ name.to_s.tr("_", "-")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,29 @@
1
+ module Basecamp
2
+ class Resource < ActiveResource::Base #:nodoc:
3
+ class << self
4
+ def parent_resources(*parents)
5
+ @parent_resources = parents
6
+ end
7
+
8
+ def element_name
9
+ name.split(/::/).last.underscore
10
+ end
11
+
12
+ def prefix_source
13
+ @parent_resources.map { |resource| "/#{resource.to_s.pluralize}/:#{resource}_id/" }.join
14
+ end
15
+
16
+ def prefix(options={})
17
+ if options.any?
18
+ options.map { |name, value| "/#{name.to_s.chomp('_id').pluralize}/#{value}/" }.join
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+
25
+ def prefix_options
26
+ id ? {} : super
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Basecamp
2
+ class TimeEntry < Resource
3
+ parent_resources :project, :todo_item
4
+
5
+ def self.all(project_id, page=0)
6
+ find(:all, :params => { :project_id => project_id, :page => page })
7
+ end
8
+
9
+ def self.report(options={})
10
+ find(:all, :from => :report, :params => options)
11
+ end
12
+
13
+ def todo_item(options={})
14
+ @todo_item ||= todo_item_id && TodoItem.find(todo_item_id, options)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Basecamp
2
+ class TodoItem < Resource
3
+ parent_resources :todo_list
4
+
5
+ def todo_list(options={})
6
+ @todo_list ||= TodoList.find(todo_list_id, options)
7
+ end
8
+
9
+ def time_entries(options={})
10
+ @time_entries ||= TimeEntry.find(:all, :params => options.merge(:todo_item_id => id))
11
+ end
12
+
13
+ def comments(options = {})
14
+ @comments ||= Comment.find(:all, :params => options.merge(:todo_item_id => id))
15
+ end
16
+
17
+ def complete!
18
+ put(:complete)
19
+ end
20
+
21
+ def uncomplete!
22
+ put(:uncomplete)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ module Basecamp
2
+ class TodoList < Resource
3
+ parent_resources :project
4
+
5
+ # Returns all lists for a project. If complete is true, only completed lists
6
+ # are returned. If complete is false, only uncompleted lists are returned.
7
+ def self.all(project_id, complete=nil)
8
+ filter = case complete
9
+ when nil then "all"
10
+ when true then "finished"
11
+ when false then "pending"
12
+ else raise ArgumentError, "invalid value for `complete'"
13
+ end
14
+
15
+ find(:all, :params => { :project_id => project_id, :filter => filter })
16
+ end
17
+
18
+ def todo_items(options={})
19
+ @todo_items ||= TodoItem.find(:all, :params => options.merge(:todo_list_id => id))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Basecamp #:nodoc:
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/../lib/basecamp.rb'
3
+
4
+ describe Basecamp do
5
+
6
+ before(:each) do
7
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
8
+ @basecamp = Basecamp::Base.new
9
+ end
10
+
11
+ describe "Creating a resource" do
12
+ it "should create a comment for post" do
13
+ comment = Basecamp::Comment.new(:post_id => 15797423)
14
+ comment.body = "test comment"
15
+ comment.save
16
+ c = Basecamp::Comment.find(comment.id)
17
+ c.body.should == "test comment"
18
+ c.id.should == comment.id.to_i
19
+ end
20
+ end
21
+
22
+ describe "Finding a resource" do
23
+ it "should find message" do
24
+ message = Basecamp::Message.find(15797423)
25
+ message.body.should_not be_blank
26
+ message.category_id.should_not be_blank
27
+ end
28
+ end
29
+
30
+ describe "Updating a Resource" do
31
+ it "should update message" do
32
+ m = Basecamp::Message.find(15797423)
33
+ m.body = 'Changed'
34
+ m.save
35
+ message = Basecamp::Message.find(15797423)
36
+ message.body.should == 'Changed'
37
+ end
38
+ end
39
+
40
+ describe "Deleting a Resource" do
41
+ it "should delete todo item" do
42
+ todo = Basecamp::TodoItem.create(:todo_list_id => 4501767, :content => 'Todo for destroy')
43
+ Basecamp::TodoItem.delete(todo.id)
44
+ end
45
+ end
46
+
47
+ describe "Using the non-REST inteface" do
48
+ it "should return array of projects" do
49
+ @basecamp.projects.should be_kind_of(Array)
50
+ end
51
+
52
+ it "should return valid project with name" do
53
+ @basecamp.projects.first.name.should_not be_empty
54
+ end
55
+
56
+ it "should find person" do
57
+ person = @basecamp.person(2926255)
58
+ person.should_not be_blank
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/../../lib/basecamp.rb'
3
+
4
+ describe Basecamp::Base do
5
+
6
+ before(:each) do
7
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
8
+ @basecamp = Basecamp::Base.new
9
+ end
10
+
11
+ it "should return the list of all accessible projects" do
12
+ @basecamp.projects.should_not be_blank
13
+ @basecamp.projects.should be_kind_of(Array)
14
+ end
15
+
16
+ it "should return valid project with name" do
17
+ @basecamp.projects.first.name.should_not be_empty
18
+ end
19
+
20
+ it "should find person" do
21
+ person = @basecamp.person(2926255)
22
+ person.should_not be_blank
23
+ end
24
+
25
+ it "should return the list of message categories for the given project" do
26
+ categories = @basecamp.message_categories(2388627)
27
+ categories.should_not be_blank
28
+ categories.should be_kind_of(Array)
29
+ end
30
+
31
+ # CONTACT MANAGEMENT
32
+
33
+ it "should return information for the company with the given id" do
34
+ company = @basecamp.company(1098114)
35
+ company.should_not be_blank
36
+ end
37
+
38
+ it "should return an array of the people in the given company" do
39
+ people = @basecamp.people(1098114)
40
+ people.should_not be_blank
41
+ people.should be_kind_of(Array)
42
+ end
43
+
44
+ it "should return information about the person with the given id" do
45
+ @basecamp.person(2926255).should_not be_nil
46
+ end
47
+
48
+ # MILESTONES
49
+
50
+ it "should return a list of all milestones for the given project" do
51
+ milestones = @basecamp.milestones(2388627)
52
+ milestones.should be_kind_of(Array)
53
+ milestones.should_not be_blank
54
+ end
55
+
56
+ it "should complete milestone with the given id" do
57
+ milestone = @basecamp.milestones(2388627).first
58
+ @basecamp.complete_milestone(milestone.id)
59
+
60
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
61
+
62
+ milestone = @basecamp.milestones(2388627).first
63
+ milestone.completed.should == true
64
+ end
65
+
66
+ it "should uncomplete milestone with the given id" do
67
+ milestone = @basecamp.milestones(2388627).first
68
+ @basecamp.uncomplete_milestone(milestone.id)
69
+
70
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
71
+
72
+ milestone = @basecamp.milestones(2388627).first
73
+ milestone.completed.should == false
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/../../lib/basecamp.rb'
3
+
4
+ describe Basecamp::Message do
5
+
6
+ before(:each) do
7
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
8
+ end
9
+
10
+ it "should return the most recent 25 messages in the given project" do
11
+ messages = Basecamp::Message.list(2388627)
12
+ messages.should_not be_blank
13
+ messages.should be_kind_of(Array)
14
+ messages.size.should_not > 25
15
+ end
16
+
17
+ it "should get a list of messages for given project and specified category" do
18
+ messages = Basecamp::Message.list(2388627, :category_id => 23864732)
19
+ messages.should_not be_blank
20
+ messages.should be_kind_of(Array)
21
+ messages.each{ |m| m.category_id.should == 23864732 }
22
+ end
23
+
24
+ it "should return a summary of all messages in the given project" do
25
+ messages = Basecamp::Message.archive(2388627)
26
+ messages.should_not be_blank
27
+ messages.should be_kind_of(Array)
28
+ end
29
+
30
+ it "should return a summary of all messages in the given project and specified category " do
31
+ messages = Basecamp::Message.archive(2388627, :category_id => 23864732)
32
+ messages.should_not be_blank
33
+ messages.should be_kind_of(Array)
34
+ end
35
+
36
+ it "should return list of message comments" do
37
+ message = Basecamp::Message.find(15797423)
38
+ comments = message.comments
39
+ comments.should_not be_blank
40
+ comments.should be_kind_of(Array)
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/../../lib/basecamp.rb'
3
+
4
+ describe Basecamp::TodoItem do
5
+
6
+ before(:each) do
7
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
8
+ end
9
+
10
+ it "should have todo list" do
11
+ todo = Basecamp::TodoItem.find(32416216)
12
+ todo.todo_list.should_not be_blank
13
+ end
14
+
15
+ it "should return list of comments" do
16
+ todo = Basecamp::TodoItem.find(32416216)
17
+ todo.comments.should_not be_blank
18
+ todo.comments.should be_kind_of(Array)
19
+ end
20
+
21
+ it "should complete todo item" do
22
+ todo = Basecamp::TodoItem.find(32416216)
23
+ todo.complete!
24
+
25
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
26
+
27
+ todo = Basecamp::TodoItem.find(32416216)
28
+ todo.completed.should == true
29
+ end
30
+
31
+ it "should uncomplete todo item" do
32
+ todo = Basecamp::TodoItem.find(32416216)
33
+ todo.uncomplete!
34
+
35
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
36
+
37
+ todo = Basecamp::TodoItem.find(32416216)
38
+ todo.completed.should == false
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/../../lib/basecamp.rb'
3
+
4
+ describe Basecamp::TodoList do
5
+
6
+ before(:each) do
7
+ Basecamp::Base.establish_connection!('flatsoft-test.grouphub.com', 'flatsoft', '123456')
8
+ end
9
+
10
+ it "should return todo items" do
11
+ list = Basecamp::TodoList.find(4501767)
12
+ list.todo_items.should_not be_blank
13
+ list.todo_items.should be_kind_of(Array)
14
+ end
15
+
16
+ it "should return all lists for a specified project" do
17
+ list = Basecamp::TodoList.all(2388627)
18
+ list.should_not be_blank
19
+ end
20
+
21
+ it "should return all finished lists for a specified project" do
22
+ list = Basecamp::TodoList.all(2388627, true)
23
+ list.should_not be_blank
24
+ list.each { |item| item.complete.should == "true" }
25
+ end
26
+
27
+ it "should return all pending lists for a specified project" do
28
+ list = Basecamp::TodoList.all(2388627, false)
29
+ list.should_not be_blank
30
+ list.each { |item| item.complete.should == "false" }
31
+ end
32
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,7 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,33 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'spec'
6
+ end
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
17
+ desc "Run the specs under spec/models"
18
+ Spec::Rake::SpecTask.new do |t|
19
+ t.spec_opts = ['--options', "spec/spec.opts"]
20
+ t.spec_files = FileList['spec/*_spec.rb']
21
+ end
22
+
23
+ namespace :spec do
24
+ desc "Run all specs in spec directory with RCov (excluding plugin specs)"
25
+ Spec::Rake::SpecTask.new(:rcov) do |t|
26
+ t.spec_opts = ['--options', '"spec/spec.opts"']
27
+ t.spec_files = FileList['spec/**/*_spec.rb']
28
+ t.rcov = true
29
+ t.rcov_opts = lambda do
30
+ IO.readlines('spec/rcov.opts').map {|l| l.chomp.split " "}.flatten
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: turingstudio-basecamp-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - The Turing Studio, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-25 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: xml-simple
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.11
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activeresource
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.2
34
+ version:
35
+ description: FIX (describe your package)
36
+ email:
37
+ - operations@turingstudio.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - README.rdoc
44
+ files:
45
+ - History.txt
46
+ - README.rdoc
47
+ - lib/basecamp.rb
48
+ - lib/basecamp/base.rb
49
+ - lib/basecamp/version.rb
50
+ - lib/basecamp/resource.rb
51
+ - lib/basecamp/attachment.rb
52
+ - lib/basecamp/comment.rb
53
+ - lib/basecamp/connection.rb
54
+ - lib/basecamp/message.rb
55
+ - lib/basecamp/record.rb
56
+ - lib/basecamp/time_entry.rb
57
+ - lib/basecamp/todoitem.rb
58
+ - lib/basecamp/todolist.rb
59
+ - spec/basecamp_spec.rb
60
+ - spec/spec.opts
61
+ - spec/spec_helper.rb
62
+ - spec/lib/basecamp_base.rb
63
+ - spec/lib/basecamp_message_spec.rb
64
+ - spec/lib/basecamp_todo_item_spec.rb
65
+ - spec/lib/basecamp_todo_list_spec.rb
66
+ - tasks/rspec.rake
67
+ has_rdoc: true
68
+ homepage: http://github.com/turingstudio/basecamp-rb/
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.2.0
90
+ signing_key:
91
+ specification_version: 2
92
+ summary: FIX (describe your package)
93
+ test_files: []
94
+