turingstudio-basecamp-rb 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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
+