turingstudio-basecamp-rb 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/Rakefile +21 -0
  2. data/lib/basecamp/attachment.rb +23 -23
  3. data/lib/basecamp/attachment_category.rb +10 -0
  4. data/lib/basecamp/base.rb +5 -109
  5. data/lib/basecamp/company.rb +10 -0
  6. data/lib/basecamp/message.rb +24 -22
  7. data/lib/basecamp/milestone.rb +50 -0
  8. data/lib/basecamp/person.rb +18 -0
  9. data/lib/basecamp/post_category.rb +10 -0
  10. data/lib/basecamp/project.rb +22 -0
  11. data/lib/basecamp/record.rb +42 -13
  12. data/lib/basecamp/resource.rb +9 -9
  13. data/lib/basecamp/time_entry.rb +11 -9
  14. data/lib/basecamp/todoitem.rb +6 -6
  15. data/lib/basecamp/todolist.rb +16 -14
  16. data/lib/basecamp/version.rb +1 -1
  17. data/lib/basecamp.rb +7 -1
  18. data/spec/lib/basecamp/attachment_category_spec.rb +12 -0
  19. data/spec/lib/basecamp/attachment_spec.rb +26 -0
  20. data/spec/lib/basecamp/base_spec.rb +44 -0
  21. data/spec/lib/basecamp/company_spec.rb +13 -0
  22. data/spec/lib/basecamp/message_spec.rb +45 -0
  23. data/spec/lib/basecamp/milestone_spec.rb +44 -0
  24. data/spec/lib/basecamp/person_spec.rb +18 -0
  25. data/spec/lib/basecamp/post_category_spec.rb +13 -0
  26. data/spec/lib/basecamp/project_spec.rb +12 -0
  27. data/spec/lib/basecamp/record_spec.rb +57 -0
  28. data/spec/lib/basecamp/time_entry_spec.rb +23 -0
  29. data/spec/lib/basecamp/todo_item_spec.rb +38 -0
  30. data/spec/lib/basecamp/todo_list_spec.rb +36 -0
  31. data/spec/spec_helper.rb +68 -1
  32. metadata +44 -18
  33. data/spec/basecamp_spec.rb +0 -61
  34. data/spec/lib/basecamp_base.rb +0 -75
  35. data/spec/lib/basecamp_message_spec.rb +0 -42
  36. data/spec/lib/basecamp_todo_item_spec.rb +0 -40
  37. data/spec/lib/basecamp_todo_list_spec.rb +0 -32
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/basecamp/version'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('basecamp-rb', Basecamp::VERSION) do |p|
7
+ p.developer('The Turing Studio, Inc.', 'support@turingstudio.com')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.extra_deps = [
10
+ ['xml-simple','>= 1.0.11'],
11
+ ['activesupport','>= 2.2.2'],
12
+ ['activeresource','>= 2.2.2']
13
+ ]
14
+ p.extra_dev_deps = [
15
+ ['newgem', ">= #{::Newgem::VERSION}"]
16
+ ]
17
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
18
+ end
19
+
20
+ require 'newgem/tasks'
21
+ Dir['tasks/**/*.rake'].each { |t| load t }
@@ -1,28 +1,20 @@
1
+ # === Attaching Files to a Resource
2
+ #
3
+ # If the resource accepts file attachments, the +attachments+ parameter should
4
+ # be an array of Basecamp::Attachment objects. Example:
5
+ #
6
+ # a1 = Basecamp::Atachment.create('primary', File.read('primary.doc'))
7
+ # a2 = Basecamp::Atachment.create('another', File.read('another.doc'))
8
+ #
9
+ # m = Basecamp::Message.new(:project_id => 1037)
10
+ # ...
11
+ # m.attachments = [a1, a2]
12
+ # m.save # => true
13
+ #
1
14
  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
-
15
+ class Attachment
18
16
  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
-
17
+
26
18
  def initialize(filename, content, content_type = 'application/octet-stream')
27
19
  @filename, @content, @content_type = filename, content, content_type
28
20
  end
@@ -49,5 +41,13 @@ module Basecamp
49
41
  raise "Could not save attachment: #{response.message} (#{response.code})"
50
42
  end
51
43
  end
44
+
45
+ class << self
46
+ def create(filename, content)
47
+ returning new(filename, content) do |attachment|
48
+ attachment.save
49
+ end
50
+ end
51
+ end
52
52
  end
53
53
  end
@@ -0,0 +1,10 @@
1
+ module Basecamp
2
+ class AttachmentCategory < Record
3
+ class << self
4
+ # Returns the list of file categories for the given project
5
+ def list(project_id)
6
+ records "/projects/#{project_id}/attachment_categories"
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/basecamp/base.rb CHANGED
@@ -26,128 +26,24 @@ module Basecamp
26
26
  @use_xml = false
27
27
  end
28
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
29
  # Make a raw web-service request to Basecamp. This will return a Hash of
119
30
  # Arrays of the response, and may seem a little odd to the uninitiated.
120
31
  def request(path, parameters = {})
121
32
  response = Base.connection.post(path, convert_body(parameters), "Content-Type" => content_type)
122
-
33
+
123
34
  if response.code.to_i / 100 == 2
124
35
  result = XmlSimple.xml_in(response.body, 'keeproot' => true, 'contentkey' => '__content__', 'forcecontent' => true)
125
- typecast_value(result)
36
+ typecast_value(result)
126
37
  else
127
38
  raise "#{response.message} (#{response.code})"
128
39
  end
129
40
  end
130
41
 
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
-
42
+ private
43
+
148
44
  def convert_body(body)
149
45
  body = use_xml ? body.to_legacy_xml : body.to_yaml
150
- end
46
+ end
151
47
 
152
48
  def content_type
153
49
  use_xml ? "application/xml" : "application/x-yaml"
@@ -0,0 +1,10 @@
1
+ module Basecamp
2
+ class Company < Record
3
+ class << self
4
+ def find(id)
5
+ # Return information for the company with the given id
6
+ record "/contacts/company/#{id}"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -3,30 +3,32 @@ module Basecamp
3
3
  parent_resources :project
4
4
  self.element_name = 'post'
5
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
6
  def comments(options = {})
29
7
  @comments ||= Comment.find(:all, :params => options.merge(:post_id => id))
30
8
  end
9
+
10
+ class << self
11
+ # Returns the most recent 25 messages in the given project (and category,
12
+ # if specified). If you need to retrieve older messages, use the archive
13
+ # method instead. Example:
14
+ #
15
+ # Basecamp::Message.list(1037)
16
+ # Basecamp::Message.list(1037, :category_id => 7301)
17
+ #
18
+ def list(project_id, options = {})
19
+ find(:all, :params => options.merge(:project_id => project_id))
20
+ end
21
+
22
+ # Returns a summary of all messages in the given project (and category, if
23
+ # specified). The summary is simply the title and category of the message,
24
+ # as well as the number of attachments (if any). Example:
25
+ #
26
+ # Basecamp::Message.archive(1037)
27
+ # Basecamp::Message.archive(1037, :category_id => 7301)
28
+ #
29
+ def archive(project_id, options = {})
30
+ find(:all, :params => options.merge(:project_id => project_id), :from => :archive)
31
+ end
32
+ end
31
33
  end
32
34
  end
@@ -0,0 +1,50 @@
1
+ module Basecamp
2
+ class Milestone < Record
3
+ # Updates an existing milestone.
4
+ def update_attributes(params = {})
5
+ data = params.dup
6
+ move = data.delete :move
7
+ move_off_weekends = data.delete :move_off_weekends
8
+ record "/milestones/update/#{id}",
9
+ :milestone => data,
10
+ :move_upcoming_milestones => move,
11
+ :move_upcoming_milestones_off_weekends => move_off_weekends
12
+ end
13
+
14
+ # Destroys the milestone
15
+ def destroy
16
+ record "/milestones/delete/#{id}"
17
+ end
18
+
19
+ # Complete the milestone
20
+ def complete!
21
+ record "/milestones/complete/#{id}"
22
+ end
23
+
24
+ # Uncomplete the milestone
25
+ def uncomplete!
26
+ record "/milestones/uncomplete/#{id}"
27
+ end
28
+
29
+ class << self
30
+ def list(project_id, find = "all")
31
+ records "/projects/#{project_id}/milestones/list", :find => find
32
+ end
33
+
34
+ # Create a new milestone for the given project. +data+ must be hash of the
35
+ # values to set, including +title+, +deadline+, +responsible_party+, and
36
+ # +notify+.
37
+ def create(project_id, data = {})
38
+ create_milestones(project_id, [data]).first
39
+ end
40
+
41
+
42
+ # As #create_milestone, but can create multiple milestones in a single
43
+ # request. The +milestones+ parameter must be an array of milestone values as
44
+ # descrbed in #create_milestone.
45
+ def create_milestones(project_id, milestones)
46
+ records "/projects/#{project_id}/milestones/create", :milestone => milestones
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ module Basecamp
2
+ class Person < Record
3
+ class << self
4
+ # Return an array of the people in the given company. If the project-id is
5
+ # given, only people who have access to the given project will be returned.
6
+ def list(company_id, project_id = nil)
7
+ url = project_id ? "/projects/#{project_id}" : ""
8
+ url << "/contacts/people/#{company_id}"
9
+ records url
10
+ end
11
+
12
+ # Return information about the person with the given id
13
+ def find(id)
14
+ record "/contacts/person/#{id}"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Basecamp
2
+ class PostCategory < Record
3
+ class << self
4
+ # Returns the list of message categories for the given project
5
+ def list(project_id)
6
+ records "/projects/#{project_id}/post_categories"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module Basecamp
2
+ class Project < Record
3
+ class << self
4
+ # Return the list of all accessible projects
5
+ def list
6
+ records "/project/list"
7
+ end
8
+ end
9
+
10
+ # def messages(options = {})
11
+ # Message.list(id, options)
12
+ # end
13
+ #
14
+ # def message_archive(options = {})
15
+ # @message_archive ||= Message.archive(id, options)
16
+ # end
17
+ #
18
+ # def milestones(find="all")
19
+ # Milestone.list(id, find)
20
+ # end
21
+ end
22
+ end
@@ -1,20 +1,22 @@
1
1
  module Basecamp
2
2
  class Record #:nodoc:
3
3
  attr_reader :type
4
-
5
- def initialize(type, hash)
6
- @type, @hash = type, hash
4
+
5
+ def initialize(hash)
6
+ # type = self.class.to_s
7
+ @hash = hash
7
8
  end
8
9
 
9
10
  def [](name)
10
- name = dashify(name)
11
+ name = name.to_s.dasherize
11
12
 
12
13
  case @hash[name]
13
14
  when Hash then
15
+ puts @hash[name].inspect
14
16
  @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) }
17
+ @hash[name].values.first.map { |v| self.class.new_with_type(@hash[name].keys.first, v) }
16
18
  else
17
- Record.new(name, @hash[name])
19
+ self.class.new_with_type(name, @hash[name])
18
20
  end
19
21
  else
20
22
  @hash[name]
@@ -30,7 +32,7 @@ module Basecamp
30
32
  end
31
33
 
32
34
  def respond_to?(sym)
33
- super || @hash.has_key?(dashify(sym))
35
+ super || @hash.has_key?(sym.to_s.dasherize)
34
36
  end
35
37
 
36
38
  def method_missing(sym, *args)
@@ -42,17 +44,44 @@ module Basecamp
42
44
  end
43
45
 
44
46
  def to_s
45
- "\#<Record(#{@type}) #{@hash.inspect[1..-2]}>"
47
+ "\#<Record(#{self.class}) #{@hash.inspect[1..-2]}>"
46
48
  end
47
49
 
48
50
  def inspect
49
51
  to_s
50
52
  end
51
53
 
52
- private
53
-
54
- def dashify(name)
55
- name.to_s.tr("_", "-")
54
+ # A convenience method for wrapping the result of a query in a Record
55
+ # object. This assumes that the result is a singleton, not a collection.
56
+ def record(path, parameters = {})
57
+ self.class.record(path, parameters)
56
58
  end
57
- end
59
+
60
+ class << self
61
+ def new_with_type(type, hash)
62
+ "Basecamp::#{type.classify}".constantize.new(hash)
63
+ end
64
+
65
+ def record(path, parameters = {})
66
+ result = Basecamp::Base.new.request(path, parameters)
67
+ (result && !result.empty?) ? new(result.values.first) : nil
68
+ end
69
+
70
+ # A convenience method for wrapping the result of a query in Record
71
+ # objects. This assumes that the result is a collection--any singleton
72
+ # result will be wrapped in an array.
73
+ def records(path, parameters = {})
74
+ result = Basecamp::Base.new.request(path, parameters)
75
+ node_singular = name.demodulize.underscore.dasherize
76
+ node_plural = node_singular.pluralize
77
+
78
+ # FIX: rename FileCategory class to AttachmentCategory
79
+ # node_plural = 'attachment-categories' if node_plural == 'file-categories'
80
+
81
+ result = result[node_plural][node_singular] or return []
82
+ result = [result] unless Array === result
83
+ result.map { |row| new(row) }
84
+ end
85
+ end
86
+ end
58
87
  end
@@ -1,29 +1,29 @@
1
1
  module Basecamp
2
2
  class Resource < ActiveResource::Base #:nodoc:
3
+ def prefix_options
4
+ id ? {} : super
5
+ end
6
+
3
7
  class << self
4
8
  def parent_resources(*parents)
5
9
  @parent_resources = parents
6
10
  end
7
-
11
+
8
12
  def element_name
9
13
  name.split(/::/).last.underscore
10
14
  end
11
-
15
+
12
16
  def prefix_source
13
17
  @parent_resources.map { |resource| "/#{resource.to_s.pluralize}/:#{resource}_id/" }.join
14
18
  end
15
-
19
+
16
20
  def prefix(options={})
17
21
  if options.any?
18
22
  options.map { |name, value| "/#{name.to_s.chomp('_id').pluralize}/#{value}/" }.join
19
23
  else
20
- super
24
+ '/'
21
25
  end
22
- end
23
- end
24
-
25
- def prefix_options
26
- id ? {} : super
26
+ end
27
27
  end
28
28
  end
29
29
  end
@@ -1,17 +1,19 @@
1
1
  module Basecamp
2
2
  class TimeEntry < Resource
3
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 })
4
+
5
+ def todo_item(options={})
6
+ @todo_item ||= todo_item_id && TodoItem.find(todo_item_id, options)
7
7
  end
8
+
9
+ class << self
10
+ def all(project_id, page=0)
11
+ find(:all, :params => { :project_id => project_id, :page => page })
12
+ end
8
13
 
9
- def self.report(options={})
10
- find(:all, :from => :report, :params => options)
14
+ def report(options={})
15
+ find(:all, :from => :report, :params => options)
16
+ end
11
17
  end
12
-
13
- def todo_item(options={})
14
- @todo_item ||= todo_item_id && TodoItem.find(todo_item_id, options)
15
- end
16
18
  end
17
19
  end
@@ -1,23 +1,23 @@
1
1
  module Basecamp
2
2
  class TodoItem < Resource
3
3
  parent_resources :todo_list
4
-
4
+
5
5
  def todo_list(options={})
6
- @todo_list ||= TodoList.find(todo_list_id, options)
6
+ @todo_list ||= TodoList.find(self.todo_list_id, options)
7
7
  end
8
-
8
+
9
9
  def time_entries(options={})
10
10
  @time_entries ||= TimeEntry.find(:all, :params => options.merge(:todo_item_id => id))
11
11
  end
12
-
12
+
13
13
  def comments(options = {})
14
14
  @comments ||= Comment.find(:all, :params => options.merge(:todo_item_id => id))
15
15
  end
16
-
16
+
17
17
  def complete!
18
18
  put(:complete)
19
19
  end
20
-
20
+
21
21
  def uncomplete!
22
22
  put(:uncomplete)
23
23
  end
@@ -1,22 +1,24 @@
1
1
  module Basecamp
2
2
  class TodoList < Resource
3
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
-
4
+
18
5
  def todo_items(options={})
19
6
  @todo_items ||= TodoItem.find(:all, :params => options.merge(:todo_list_id => id))
20
7
  end
8
+
9
+ class << self
10
+ # Returns all lists for a project. If complete is true, only completed lists
11
+ # are returned. If complete is false, only uncompleted lists are returned.
12
+ def all(project_id, complete=nil)
13
+ filter = case complete
14
+ when nil then "all"
15
+ when true then "finished"
16
+ when false then "pending"
17
+ else raise ArgumentError, "invalid value for `complete'"
18
+ end
19
+
20
+ find(:all, :params => { :project_id => project_id, :filter => filter })
21
+ end
22
+ end
21
23
  end
22
24
  end
@@ -1,3 +1,3 @@
1
1
  module Basecamp #:nodoc:
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/basecamp.rb CHANGED
@@ -8,11 +8,17 @@ require 'activeresource'
8
8
 
9
9
  require 'basecamp/base'
10
10
  require 'basecamp/resource'
11
+ require 'basecamp/record'
11
12
  require 'basecamp/attachment'
12
13
  require 'basecamp/comment'
14
+ require 'basecamp/company'
13
15
  require 'basecamp/connection'
16
+ require 'basecamp/file_category'
14
17
  require 'basecamp/message'
15
- require 'basecamp/record'
18
+ require 'basecamp/milestone'
19
+ require 'basecamp/project'
20
+ require 'basecamp/person'
21
+ require 'basecamp/post_category'
16
22
  require 'basecamp/time_entry'
17
23
  require 'basecamp/todoitem'
18
24
  require 'basecamp/todolist'
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ describe Basecamp::AttachmentCategory do
4
+ before(:each) do
5
+ establish_connection
6
+ end
7
+
8
+ it "should return the list of file categories for the given project" do
9
+ list = Basecamp::AttachmentCategory.list(TEST_PROJECT_ID)
10
+ list.should be_kind_of(Array)
11
+ end
12
+ end