mavenlink 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +10 -3
  4. data/Gemfile.lock +38 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +69 -0
  7. data/Rakefile +50 -24
  8. data/TODO +18 -0
  9. data/VERSION +1 -0
  10. data/lib/mavenlink.rb +2 -134
  11. data/lib/mavenlink/base.rb +184 -0
  12. data/lib/mavenlink/client.rb +202 -96
  13. data/mavenlink.gemspec +85 -19
  14. data/spec/mavenlink/base_spec.rb +223 -0
  15. data/spec/mavenlink/client_spec.rb +183 -0
  16. data/spec/spec_helper.rb +13 -25
  17. metadata +243 -232
  18. checksums.yaml +0 -7
  19. data/.gitignore +0 -11
  20. data/.ruby-gemset +0 -1
  21. data/.ruby-version +0 -1
  22. data/README.md +0 -241
  23. data/bin/mavenlink-console +0 -18
  24. data/doc/ml_logo_lb-primary.png +0 -0
  25. data/lib/config/specification.yml +0 -1665
  26. data/lib/mavenlink/account_invitation.rb +0 -4
  27. data/lib/mavenlink/account_membership.rb +0 -4
  28. data/lib/mavenlink/additional_item.rb +0 -4
  29. data/lib/mavenlink/assignment.rb +0 -5
  30. data/lib/mavenlink/attachment.rb +0 -11
  31. data/lib/mavenlink/backup_approver_association.rb +0 -4
  32. data/lib/mavenlink/concerns/custom_fieldable.rb +0 -9
  33. data/lib/mavenlink/concerns/indestructible.rb +0 -11
  34. data/lib/mavenlink/concerns/locked_record.rb +0 -30
  35. data/lib/mavenlink/cost_rate.rb +0 -4
  36. data/lib/mavenlink/custom_field.rb +0 -4
  37. data/lib/mavenlink/custom_field_choice.rb +0 -5
  38. data/lib/mavenlink/custom_field_value.rb +0 -4
  39. data/lib/mavenlink/errors.rb +0 -47
  40. data/lib/mavenlink/expense.rb +0 -4
  41. data/lib/mavenlink/expense_category.rb +0 -4
  42. data/lib/mavenlink/expense_report_submission.rb +0 -7
  43. data/lib/mavenlink/external_payment.rb +0 -4
  44. data/lib/mavenlink/external_reference.rb +0 -24
  45. data/lib/mavenlink/fixed_fee_item.rb +0 -4
  46. data/lib/mavenlink/holiday.rb +0 -4
  47. data/lib/mavenlink/holiday_calendar.rb +0 -4
  48. data/lib/mavenlink/holiday_calendar_association.rb +0 -4
  49. data/lib/mavenlink/holiday_calendar_membership.rb +0 -4
  50. data/lib/mavenlink/invoice.rb +0 -4
  51. data/lib/mavenlink/logger.rb +0 -62
  52. data/lib/mavenlink/model.rb +0 -279
  53. data/lib/mavenlink/organization.rb +0 -4
  54. data/lib/mavenlink/organization_membership.rb +0 -4
  55. data/lib/mavenlink/participation.rb +0 -4
  56. data/lib/mavenlink/post.rb +0 -4
  57. data/lib/mavenlink/project_template.rb +0 -4
  58. data/lib/mavenlink/project_template_assignment.rb +0 -4
  59. data/lib/mavenlink/railtie.rb +0 -7
  60. data/lib/mavenlink/rate_card.rb +0 -4
  61. data/lib/mavenlink/rate_card_role.rb +0 -4
  62. data/lib/mavenlink/rate_card_set.rb +0 -4
  63. data/lib/mavenlink/rate_card_set_version.rb +0 -30
  64. data/lib/mavenlink/rate_card_version.rb +0 -4
  65. data/lib/mavenlink/request.rb +0 -241
  66. data/lib/mavenlink/resolution.rb +0 -4
  67. data/lib/mavenlink/response.rb +0 -22
  68. data/lib/mavenlink/role.rb +0 -5
  69. data/lib/mavenlink/settings.rb +0 -11
  70. data/lib/mavenlink/skill.rb +0 -4
  71. data/lib/mavenlink/skill_category.rb +0 -4
  72. data/lib/mavenlink/skill_membership.rb +0 -4
  73. data/lib/mavenlink/specificators/association.rb +0 -13
  74. data/lib/mavenlink/specificators/attribute.rb +0 -13
  75. data/lib/mavenlink/specificators/base.rb +0 -24
  76. data/lib/mavenlink/specificators/validation.rb +0 -27
  77. data/lib/mavenlink/status_report.rb +0 -4
  78. data/lib/mavenlink/story.rb +0 -8
  79. data/lib/mavenlink/story_allocation_day.rb +0 -5
  80. data/lib/mavenlink/story_dependency.rb +0 -5
  81. data/lib/mavenlink/story_task.rb +0 -5
  82. data/lib/mavenlink/tag.rb +0 -5
  83. data/lib/mavenlink/time_adjustment.rb +0 -4
  84. data/lib/mavenlink/time_entry.rb +0 -4
  85. data/lib/mavenlink/time_off_entry.rb +0 -4
  86. data/lib/mavenlink/timesheet_submission.rb +0 -4
  87. data/lib/mavenlink/user.rb +0 -5
  88. data/lib/mavenlink/vendor.rb +0 -4
  89. data/lib/mavenlink/workspace.rb +0 -21
  90. data/lib/mavenlink/workspace_group.rb +0 -5
  91. data/lib/mavenlink/workspace_invoice_preference.rb +0 -4
  92. data/lib/mavenlink/workweek.rb +0 -4
  93. data/lib/mavenlink/workweek_membership.rb +0 -4
  94. data/spec/lib/mavenlink/account_membership_spec.rb +0 -8
  95. data/spec/lib/mavenlink/assignment_spec.rb +0 -17
  96. data/spec/lib/mavenlink/attachment_spec.rb +0 -30
  97. data/spec/lib/mavenlink/backup_approver_association_spec.rb +0 -9
  98. data/spec/lib/mavenlink/client_spec.rb +0 -187
  99. data/spec/lib/mavenlink/concerns/indestructible_spec.rb +0 -13
  100. data/spec/lib/mavenlink/concerns/locked_record_spec.rb +0 -28
  101. data/spec/lib/mavenlink/cost_rate_spec.rb +0 -9
  102. data/spec/lib/mavenlink/custom_field_value_spec.rb +0 -10
  103. data/spec/lib/mavenlink/expense_report_submission_spec.rb +0 -16
  104. data/spec/lib/mavenlink/expense_spec.rb +0 -23
  105. data/spec/lib/mavenlink/external_references_spec.rb +0 -144
  106. data/spec/lib/mavenlink/holiday_calendar_association_spec.rb +0 -8
  107. data/spec/lib/mavenlink/holiday_calendar_membership_spec.rb +0 -8
  108. data/spec/lib/mavenlink/holiday_spec.rb +0 -7
  109. data/spec/lib/mavenlink/invalid_request_error_spec.rb +0 -9
  110. data/spec/lib/mavenlink/invoice_spec.rb +0 -176
  111. data/spec/lib/mavenlink/model_spec.rb +0 -439
  112. data/spec/lib/mavenlink/post_spec.rb +0 -23
  113. data/spec/lib/mavenlink/rate_card_set_version_spec.rb +0 -119
  114. data/spec/lib/mavenlink/record_invalid_error_spec.rb +0 -16
  115. data/spec/lib/mavenlink/record_not_found_error_spec.rb +0 -9
  116. data/spec/lib/mavenlink/request_spec.rb +0 -381
  117. data/spec/lib/mavenlink/response_spec.rb +0 -50
  118. data/spec/lib/mavenlink/role_spec.rb +0 -9
  119. data/spec/lib/mavenlink/settings_spec.rb +0 -23
  120. data/spec/lib/mavenlink/skill_category_spec.rb +0 -7
  121. data/spec/lib/mavenlink/skill_membership_spec.rb +0 -9
  122. data/spec/lib/mavenlink/skill_spec.rb +0 -8
  123. data/spec/lib/mavenlink/specificators/association_spec.rb +0 -25
  124. data/spec/lib/mavenlink/specificators/attribute_spec.rb +0 -25
  125. data/spec/lib/mavenlink/specificators/validation_spec.rb +0 -39
  126. data/spec/lib/mavenlink/story_allocation_day_spec.rb +0 -64
  127. data/spec/lib/mavenlink/story_dependency_spec.rb +0 -16
  128. data/spec/lib/mavenlink/story_spec.rb +0 -69
  129. data/spec/lib/mavenlink/time_adjustment_spec.rb +0 -13
  130. data/spec/lib/mavenlink/time_entry_spec.rb +0 -43
  131. data/spec/lib/mavenlink/time_off_entry_spec.rb +0 -9
  132. data/spec/lib/mavenlink/user_spec.rb +0 -138
  133. data/spec/lib/mavenlink/workspace_group_spec.rb +0 -25
  134. data/spec/lib/mavenlink/workspace_spec.rb +0 -431
  135. data/spec/lib/mavenlink_spec.rb +0 -43
  136. data/spec/support/shared_examples.rb +0 -148
@@ -1,116 +1,222 @@
1
1
  module Mavenlink
2
- class Client
3
- ENDPOINT = 'https://api.mavenlink.com/api/v1/'.freeze
4
-
5
- # @param settings [ActiveSuppport::HashWithIndifferentAccess]
6
- def initialize(settings = Mavenlink.default_settings)
7
- @settings = settings
8
- @oauth_token = settings[:oauth_token] or raise ArgumentError, 'OAuth token is not set'
9
- @endpoint = settings[:endpoint] || ENDPOINT
10
- @use_json = settings[:use_json]
11
- if settings.key?(:user_agent_override)
12
- @user_agent_override = settings[:user_agent_override]
13
- end
2
+ class Base
3
+ base_uri ENV['TESTING'] ? 'https://mavenlink.local/api/v0' : 'https://www.mavenlink.com/api/v0'
4
+ debug ENV['DEBUG'] || false
5
+ end
14
6
 
15
- # TODO: implement with method_missing?
16
- # Declare API calls client.-->>workspaces<<---.create({})
17
- Mavenlink.specification.keys.each do |collection_name|
18
- singleton_class.instance_eval do
19
- define_method collection_name do
20
- ::Mavenlink::Request.new(collection_name, self)
21
- end
22
- end
23
- end
7
+ # Wrapping objects that have no API endpoints yet
8
+
9
+ class User < Base
10
+ request_path "/not_available_yet"
11
+ class_name :user
12
+ end
13
+
14
+ class Asset < Base
15
+ request_path "/not_available_yet"
16
+ class_name :asset
17
+ end
18
+
19
+ # Normal API objects
20
+
21
+ class Client < Base
22
+ def initialize(user_id, token)
23
+ super({}, :basic_auth => {:username => user_id, :password => token})
24
24
  end
25
25
 
26
- # @return [Faraday::Connection]
27
- def connection
28
- Faraday.new(connection_options) do |builder|
29
- if @use_json
30
- builder.headers['Content-Type'] = 'application/json'
31
- else
32
- builder.use Faraday::Request::UrlEncoded
33
- end
34
- builder.adapter(*Mavenlink.adapter)
35
- end
26
+ def workspaces(options = {})
27
+ fetch('workspaces', Workspace, options)
36
28
  end
37
29
 
38
- # Performs custom GET request
39
- # @param [String] path
40
- # @param [Hash] arguments
41
- def get(path, arguments = {})
42
- Mavenlink.logger.note "Started GET /#{path} with #{arguments.inspect}"
43
- parse_request(connection.get(path, arguments).body)
30
+ def workspace(workspace_id)
31
+ fetch("workspaces/#{workspace_id}", Workspace, {})
44
32
  end
45
33
 
46
- # Performs custom POST request
47
- # @param [String] path
48
- # @param [Hash] arguments
49
- def post(path, arguments = {})
50
- Mavenlink.logger.note "Started POST /#{path} with #{arguments.inspect}"
51
- parse_request(connection.post(path, arguments).body)
34
+
35
+ def time_entries(options = {})
36
+ fetch('time_entries', TimeEntry, options, lambda { |time_entry| {:workspace_id => time_entry['workspace_id']} })
52
37
  end
53
38
 
54
- # Performs custom PUT request
55
- # @param [String] path
56
- # @param [Hash] arguments
57
- def put(path, arguments = {})
58
- Mavenlink.logger.note "Started PUT /#{path} with #{arguments.inspect}"
59
- parse_request(connection.put(path, arguments).body)
39
+ def time_entry(time_entry_id)
40
+ fetch("time_entries/#{time_entry_id}", TimeEntry, {}, lambda { |time_entry| {:workspace_id => time_entry['workspace_id']} })
60
41
  end
61
42
 
62
- # Performs custom PUT request
63
- # @param [String] path
64
- # @param [Hash] arguments
65
- def delete(path, arguments = {})
66
- Mavenlink.logger.note "Started DELETE /#{path} with #{arguments.inspect}"
67
- parse_request(connection.delete(path, arguments).body)
43
+
44
+ def expenses(options = {})
45
+ fetch('expenses', Expense, options, lambda { |expense| {:workspace_id => expense['workspace_id']} })
68
46
  end
69
47
 
70
- private
48
+ def expense(expense_id)
49
+ fetch("expenses/#{expense_id}", Expense, {}, lambda { |expense| {:workspace_id => expense['workspace_id']} })
50
+ end
71
51
 
72
- attr_reader :oauth_token, :endpoint
73
52
 
74
- # @return [Hash]
75
- def connection_options
76
- if @user_agent_override && @user_agent_override.length > 1
77
- user_agent = "#{@user_agent_override}"
78
- else
79
- user_agent = "Mavenlink Ruby Gem"
80
- end
81
- {
82
- headers: { 'Accept' => "application/json",
83
- 'User-Agent' => "#{user_agent}",
84
- 'Authorization' => "Bearer #{oauth_token}" },
85
- ssl: { verify: false },
86
- url: endpoint
87
- }.freeze
88
- end
89
-
90
- def parse_request(response)
91
- if response.present?
92
- parsed_response = JSON.parse(response)
93
- else
94
- return
95
- end
53
+ def invoices(options = {})
54
+ fetch('invoices', Invoice, options, lambda { |invoice| {:workspace_id => invoice['workspace_id']} })
55
+ end
96
56
 
97
- parsed_response.tap do
98
- Mavenlink.logger.whisper 'Received response:'
99
- Mavenlink.logger.inspection response
100
-
101
- case parsed_response
102
- when Array
103
- Mavenlink.logger.whisper 'Returned as a plain collection'
104
- when Hash
105
- if parsed_response['errors']
106
- Mavenlink.logger.disappointment 'REQUEST FAILED:'
107
- Mavenlink.logger.inspection parsed_response['errors']
108
- raise InvalidRequestError.new(parsed_response)
109
- end
110
- end
111
- end
112
- rescue JSON::ParserError => e
113
- raise Mavenlink::InvalidResponseError.new(e.message)
57
+ def invoice(invoice_id)
58
+ fetch("invoices/#{invoice_id}", Invoice, {}, lambda { |invoice| {:workspace_id => invoice['workspace_id']} })
59
+ end
60
+
61
+
62
+ def events(options = {})
63
+ fetch('events', Event, options)
64
+ end
65
+ end
66
+
67
+ class Workspace < Base
68
+ request_path "/workspaces/:id"
69
+ class_name :workspace
70
+
71
+ def posts(options = {})
72
+ fetch('posts', Post, options, :workspace_id => id)
73
+ end
74
+
75
+ def post(post_id, options = {})
76
+ fetch("posts/#{post_id}", Post, options, :workspace_id => id)
77
+ end
78
+
79
+ def create_post(options)
80
+ build("posts", Post, options, :workspace_id => id)
81
+ end
82
+
83
+
84
+ def time_entries(options = {})
85
+ fetch("time_entries", TimeEntry, options, :workspace_id => id)
86
+ end
87
+
88
+ def time_entry(time_entry_id, options = {})
89
+ fetch("time_entries/#{time_entry_id}", TimeEntry, options, :workspace_id => id)
90
+ end
91
+
92
+ def create_time_entry(options)
93
+ build("time_entries", TimeEntry, options, :workspace_id => id)
94
+ end
95
+
96
+
97
+ def expenses(options = {})
98
+ fetch("expenses", Expense, options, :workspace_id => id)
99
+ end
100
+
101
+ def expense(expense_id, options = {})
102
+ fetch("expenses/#{expense_id}", Expense, options, :workspace_id => id)
103
+ end
104
+
105
+ def create_expense(options)
106
+ build("expenses", Expense, options, :workspace_id => id)
107
+ end
108
+
109
+
110
+ def invoices(options = {})
111
+ fetch("invoices", Invoice, options, :workspace_id => id)
112
+ end
113
+
114
+ def invoice(invoice_id, options = {})
115
+ fetch("invoice/#{invoice_id}", Invoice, options, :workspace_id => id)
116
+ end
117
+
118
+
119
+ def stories(options = {})
120
+ fetch("stories", Story, options, :workspace_id => id)
114
121
  end
122
+
123
+ def story(story_id, options = {})
124
+ fetch("stories/#{story_id}", Story, options, :workspace_id => id)
125
+ end
126
+
127
+ def create_story(options)
128
+ build("stories", Story, options, :workspace_id => id)
129
+ end
130
+
131
+
132
+ def participants(options = {})
133
+ fetch("participants", Participant, options, :workspace_id => id)
134
+ end
135
+ end
136
+
137
+ class Participant < Base
138
+ request_path "/workspaces/:workspace_id/participants/:id"
139
+ class_name :participant
140
+ end
141
+
142
+ class Post < Base
143
+ request_path "/workspaces/:workspace_id/posts/:id"
144
+ contains :user => User,
145
+ :google_documents => Asset,
146
+ :assets => Asset,
147
+ :replies => lambda {|parent, child| { :class => Post, :path_params => { :workspace_id => child['workspace_id'] } } }
148
+ class_name :post
149
+
150
+ def story(options = {})
151
+ fetch("../../stories/#{json['story_id']}", Story, options, :workspace_id => workspace_id) if json['story_id']
152
+ end
153
+
154
+ def workspace(options = {})
155
+ fetch("../..", Workspace, options)
156
+ end
157
+ end
158
+
159
+ class TimeEntry < Base
160
+ request_path "/workspaces/:workspace_id/time_entries/:id"
161
+ class_name :time_entry
162
+
163
+ def story(options = {})
164
+ fetch("../../stories/#{json['story_id']}", Story, options, :workspace_id => workspace_id) if json['story_id']
165
+ end
166
+
167
+ def workspace(options = {})
168
+ fetch("../..", Workspace, options)
169
+ end
170
+ end
171
+
172
+ class Expense < Base
173
+ request_path "/workspaces/:workspace_id/expenses/:id"
174
+ class_name :expense
175
+
176
+ def workspace(options = {})
177
+ fetch("../..", Workspace, options)
178
+ end
179
+ end
180
+
181
+ class AdditionalItem < Base
182
+ request_path "/not_available_yet"
183
+ class_name :additional_item
184
+ end
185
+
186
+ class Invoice < Base
187
+ request_path "/workspaces/:workspace_id/invoices/:id"
188
+ class_name :invoice
189
+ contains :time_entries => lambda { |time_entry, json| { :class => TimeEntry, :path_params => { :id => json['id'], :workspace_id => json['workspace_id'] } } },
190
+ :expenses => lambda { |expense, json| { :class => Expense, :path_params => { :id => json['id'], :workspace_id => json['workspace_id'] } } },
191
+ :additional_items => AdditionalItem
192
+
193
+ def workspace(options = {})
194
+ fetch("../..", Workspace, options)
195
+ end
196
+ end
197
+
198
+ class Story < Base
199
+ request_path "/workspaces/:workspace_id/stories/:id"
200
+ contains :creator => User, :assignee => User
201
+ class_name :story
202
+
203
+ def workspace(options = {})
204
+ fetch("../..", Workspace, options)
205
+ end
206
+ end
207
+
208
+ class Event < Base
209
+ request_path "/events/:id"
210
+ contains :subject => lambda { |event, json|
211
+ case event.subject_type
212
+ when "Post"
213
+ { :class => Post, :path_params => { :workspace_id => json['workspace_id'] } }
214
+ when "Story"
215
+ { :class => Story, :path_params => { :workspace_id => json['workspace_id'] } }
216
+ else
217
+ raise "Unknown event subject type: #{event.subject_type}"
218
+ end
219
+ }
220
+ class_name :event
115
221
  end
116
222
  end
@@ -1,29 +1,95 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{mavenlink}
5
- s.version = "0.0.1"
6
-
7
- s.date = %q{2014-04-01}
8
- s.authors = ["Mavenlink"]
9
- s.email = %q{opensource@mavenlink.com}
10
- s.homepage = %q{http://github.com/mavenlink/mavenlink_gem}
8
+ s.version = "0.2.0"
11
9
 
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Roger Neel", "Andrew Cantino"]
12
+ s.date = %q{2011-06-30}
13
+ s.description = %q{This is a Ruby API client for Mavenlink. Mavenlink's project collaboration suite allows you to manage your business relationships, share files, and track project activity online from anywhere in the world. Within a project workspace in Mavenlink, you can agree on budget & schedule, track time, send invoices, get paid via PayPal, and complete work.}
14
+ s.email = %q{support@mavenlink.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc",
18
+ "TODO"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".rspec",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "TODO",
29
+ "VERSION",
30
+ "lib/mavenlink.rb",
31
+ "lib/mavenlink/base.rb",
32
+ "lib/mavenlink/client.rb",
33
+ "mavenlink.gemspec",
34
+ "spec/mavenlink/base_spec.rb",
35
+ "spec/mavenlink/client_spec.rb",
36
+ "spec/spec_helper.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/mavenlink/mavenlink_ruby_api}
12
39
  s.licenses = ["MIT"]
13
-
14
- s.files = `git ls-files`.split("\n")
15
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
40
  s.require_paths = ["lib"]
17
- s.extra_rdoc_files = ["README.md"]
41
+ s.rubygems_version = %q{1.3.7}
42
+ s.summary = %q{Ruby client for Mavenlink's API}
43
+ s.test_files = [
44
+ "spec/mavenlink/base_spec.rb",
45
+ "spec/mavenlink/client_spec.rb",
46
+ "spec/spec_helper.rb"
47
+ ]
18
48
 
19
- s.description = %q{Simple Ruby API for the Mavenlink API}
20
- s.summary = %q{Mavenlink API Ruby Wrapper}
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
21
52
 
22
- s.add_runtime_dependency 'activesupport', ">= 4.0.4"
23
- s.add_runtime_dependency 'activemodel', ">= 4.0.4"
24
- s.add_runtime_dependency 'brainstem-adaptor', ">= 0.0.3"
25
- s.add_runtime_dependency 'faraday', ">= 0.9.0"
26
- s.add_development_dependency 'rspec', "2.14.1"
27
- s.add_development_dependency 'shoulda-matchers', "2.5.0"
28
- s.add_development_dependency 'awesome_print'
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<httparty>, ["= 0.7.4"])
55
+ s.add_runtime_dependency(%q<webmock>, ["~> 1.6.2"])
56
+ s.add_runtime_dependency(%q<rspec>, ["~> 2.3.0"])
57
+ s.add_runtime_dependency(%q<jeweler>, ["~> 1.5.2"])
58
+ s.add_runtime_dependency(%q<rr>, ["~> 1.0.2"])
59
+ s.add_runtime_dependency(%q<json>, ["~> 1.5.1"])
60
+ s.add_runtime_dependency(%q<httparty>, ["= 0.7.4"])
61
+ s.add_development_dependency(%q<webmock>, ["~> 1.6.2"])
62
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
63
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
64
+ s.add_development_dependency(%q<rr>, ["~> 1.0.2"])
65
+ s.add_development_dependency(%q<json>, ["~> 1.5.1"])
66
+ else
67
+ s.add_dependency(%q<httparty>, ["= 0.7.4"])
68
+ s.add_dependency(%q<webmock>, ["~> 1.6.2"])
69
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
71
+ s.add_dependency(%q<rr>, ["~> 1.0.2"])
72
+ s.add_dependency(%q<json>, ["~> 1.5.1"])
73
+ s.add_dependency(%q<httparty>, ["= 0.7.4"])
74
+ s.add_dependency(%q<webmock>, ["~> 1.6.2"])
75
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
76
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
77
+ s.add_dependency(%q<rr>, ["~> 1.0.2"])
78
+ s.add_dependency(%q<json>, ["~> 1.5.1"])
79
+ end
80
+ else
81
+ s.add_dependency(%q<httparty>, ["= 0.7.4"])
82
+ s.add_dependency(%q<webmock>, ["~> 1.6.2"])
83
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
84
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
85
+ s.add_dependency(%q<rr>, ["~> 1.0.2"])
86
+ s.add_dependency(%q<json>, ["~> 1.5.1"])
87
+ s.add_dependency(%q<httparty>, ["= 0.7.4"])
88
+ s.add_dependency(%q<webmock>, ["~> 1.6.2"])
89
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
90
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
91
+ s.add_dependency(%q<rr>, ["~> 1.0.2"])
92
+ s.add_dependency(%q<json>, ["~> 1.5.1"])
93
+ end
29
94
  end
95
+
@@ -0,0 +1,223 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe Mavenlink::Base do
4
+ describe "request_path and join_paths" do
5
+ class Example < Mavenlink::Base
6
+ request_path "/:something/blah/foo/:id"
7
+ end
8
+
9
+ describe "request_path" do
10
+ it "should interpolate id and any path_params into the request_path string" do
11
+ Example.new({ 'id' => 2 }, :path_params => { :something => "hello" }).request_path.should == "/hello/blah/foo/2"
12
+ end
13
+ end
14
+
15
+ describe "join_paths" do
16
+ it "should combine paths, respecting leading ..'s" do
17
+ Example.new({}).join_paths("/hello/world", "").should == "/hello/world"
18
+ Example.new({}).join_paths("", "hi").should == "/hi"
19
+ Example.new({}).join_paths("", "/hi").should == "/hi"
20
+ Example.new({}).join_paths("/hello/world", "../there").should == "/hello/there"
21
+ Example.new({}).join_paths("/", "there").should == "/there"
22
+ Example.new({}).join_paths("/hello/world", "/again").should == "/hello/world/again"
23
+ Example.new({}).join_paths("/hello/world/", "/again").should == "/hello/world/again"
24
+ Example.new({}).join_paths("/hello/world/", "again").should == "/hello/world/again"
25
+ Example.new({}).join_paths("/hello/world", "../../../there").should == "/there"
26
+ Example.new({}).join_paths("hi", "there").should == "/hi/there"
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "example usage with a test client and some widgets with many prongs" do
32
+ class TestBase < Mavenlink::Base
33
+ base_uri 'http://www.example.com/api/v0'
34
+ debug false
35
+ end
36
+
37
+ class TestClient < TestBase
38
+ def initialize(username, password)
39
+ super({}, :basic_auth => { :username => username, :password => password })
40
+ end
41
+
42
+ def widgets(options = {})
43
+ fetch('widgets', Widget, options)
44
+ end
45
+ end
46
+
47
+ class User < TestBase; end
48
+
49
+ class Prong < TestBase
50
+ request_path "/widgets/:widget_id/prongs/:id"
51
+ class_name "prong"
52
+ end
53
+
54
+ class Widget < TestBase
55
+ request_path "/widgets/:id"
56
+ contains :owners => User
57
+ class_name "widget"
58
+
59
+ def prongs(options = {})
60
+ fetch("prongs", Prong, options, :widget_id => id)
61
+ end
62
+
63
+ def prong(prong_id)
64
+ fetch("prongs/#{prong_id}", Prong, {}, :widget_id => id)
65
+ end
66
+
67
+ def create_prong(options)
68
+ build("prongs", Prong, options, :widget_id => id)
69
+ end
70
+ end
71
+
72
+ before do
73
+ stub_request(:get, "http://user:password@www.example.com/api/v0/widgets?limit=5").
74
+ with(:headers => {'Accept'=>'application/json'}).
75
+ to_return(:status => 200, :body => [{ :kind => "sprocket", :id => 1, :owners => [{ :name => "Bob" }, { :name => "Sam" }] }, { :kind => "gizmo", :id => 2 }].to_json, :headers => {})
76
+ end
77
+
78
+ describe "the test client" do
79
+ it "should do basic auth" do
80
+ client = TestClient.new("user", "password")
81
+ client.widgets(:limit => 5)
82
+ WebMock.should have_requested(:get, "http://user:password@www.example.com/api/v0/widgets?limit=5")
83
+ end
84
+
85
+ it "should have many widgets" do
86
+ client = TestClient.new("user", "password")
87
+ widgets = client.widgets(:limit => 5)
88
+ widgets.length.should == 2
89
+ widgets.first.kind.should == "sprocket"
90
+ widgets.first.owners.length.should == 2
91
+ widgets.first.owners.first.should be_a(User)
92
+ widgets.first.owners.first.name.should == "Bob"
93
+ widgets.last.owners.should be_nil
94
+ end
95
+ end
96
+
97
+ describe "widgets" do
98
+ before do
99
+ @client = TestClient.new("user", "password")
100
+ @widget = @client.widgets(:limit => 5).first
101
+ @widget.should be_a(Widget)
102
+ end
103
+
104
+ it "should be reloadable" do
105
+ stub_request(:get, "http://user:password@www.example.com/api/v0/widgets/1?some=option").
106
+ with(:headers => {'Accept'=>'application/json'}).
107
+ to_return(:status => 200, :body => { :kind => "sprocket_updated", :id => 1, :owners => [{ :name => "Bob_updated" }, { :name => "Sam" }] }.to_json, :headers => {})
108
+
109
+ @widget.reload(:some => :option)
110
+ @widget.kind.should == "sprocket_updated"
111
+ @widget.owners.first.name.should == "Bob_updated"
112
+ end
113
+
114
+ describe "#prongs" do
115
+ before do
116
+ stub_request(:get, "http://user:password@www.example.com/api/v0/widgets/1/prongs?exclude=gears").
117
+ with(:headers => {'Accept'=>'application/json'}).
118
+ to_return(:status => 200, :body => [{ :price => "$5", :id => 100 }, { :price => "$10", :id => 101 }].to_json, :headers => {})
119
+
120
+ @prongs = @widget.prongs(:exclude => "gears")
121
+ end
122
+
123
+ it "should return prongs" do
124
+ @prongs.length.should == 2
125
+ @prongs.first.should be_a(Prong)
126
+ @prongs.first.price.should == "$5"
127
+ @prongs.first.id.should == 100
128
+ @prongs.first.request_path.should == "/widgets/1/prongs/100"
129
+ @prongs.first.path_params.should == { :widget_id => 1 }
130
+ end
131
+
132
+ describe "#update" do
133
+ it "updates the local model when successful" do
134
+ stub_request(:put, "http://user:password@www.example.com/api/v0/widgets/1/prongs/100").
135
+ with(:headers => {'Accept'=>'application/json'}, :body => "prong[price]=%248.50").
136
+ to_return(:status => 200, :body => { :price => "$8.50", :id => 100 }.to_json, :headers => {})
137
+
138
+ prong = @prongs.first
139
+ prong.update(:price => "$8.50")
140
+ prong.price.should == "$8.50"
141
+ prong.errors.should be_empty
142
+ end
143
+
144
+ it "doesn't update the local model but adds errors when unsuccessful" do
145
+ stub_request(:put, "http://user:password@www.example.com/api/v0/widgets/1/prongs/100").
146
+ with(:headers => {'Accept'=>'application/json'}, :body => "prong[price]=%248.50").
147
+ to_return(:status => 422, :body => { :errors => ["error 1", "error 2"] }.to_json, :headers => {})
148
+
149
+ prong = @prongs.first
150
+ prong.update(:price => "$8.50")
151
+ prong.price.should == "$5"
152
+ prong.errors.should == ["error 1", "error 2"]
153
+ end
154
+ end
155
+
156
+ describe "#destroy" do
157
+ it "returns true when successful" do
158
+ stub_request(:delete, "http://user:password@www.example.com/api/v0/widgets/1/prongs/100?").
159
+ with(:headers => {'Accept'=>'application/json'}).
160
+ to_return(:status => 200, :body => '', :headers => {})
161
+
162
+ prong = @prongs.first
163
+ prong.destroy.should be_true
164
+ end
165
+
166
+ it "returns false and sets errors on failure" do
167
+ stub_request(:delete, "http://user:password@www.example.com/api/v0/widgets/1/prongs/100?").
168
+ with(:headers => {'Accept'=>'application/json'}).
169
+ to_return(:status => 422, :body => { :errors => ["error 1", "error 2"] }.to_json, :headers => {})
170
+
171
+ prong = @prongs.first
172
+ prong.destroy.should be_false
173
+ prong.errors.should == ["error 1", "error 2"]
174
+ end
175
+ end
176
+ end
177
+
178
+ describe "#prong" do
179
+ it "should return a single prong by id" do
180
+ stub_request(:get, "http://user:password@www.example.com/api/v0/widgets/1/prongs/100?").
181
+ with(:headers => {'Accept'=>'application/json'}).
182
+ to_return(:status => 200, :body => { :price => "$5", :id => 100 }.to_json, :headers => {})
183
+ prong = @widget.prong(100)
184
+ prong.should be_a(Prong)
185
+ prong.price.should == "$5"
186
+ end
187
+ end
188
+
189
+ describe "#create_prong" do
190
+ it "should return the new prong object when successful" do
191
+ stub_request(:post, "http://user:password@www.example.com/api/v0/widgets/1/prongs").
192
+ with(:headers => {'Accept'=>'application/json'}, :body => "prong[price]=%24100").
193
+ to_return(:status => 200, :body => { :price => "$100", :id => 102 }.to_json, :headers => {})
194
+
195
+ prong = @widget.create_prong(:price => "$100")
196
+ prong.request_path.should == "/widgets/1/prongs/102"
197
+ prong.should be_a(Prong)
198
+ prong.price.should == "$100"
199
+ prong.id.should == 102
200
+ prong.errors.should be_empty
201
+
202
+ stub_request(:put, "http://user:password@www.example.com/api/v0/widgets/1/prongs/102").
203
+ with(:headers => {'Accept'=>'application/json'}, :body => "prong[price]=%2490").
204
+ to_return(:status => 200, :body => { :price => "$90", :id => 102 }.to_json, :headers => {})
205
+ prong.update(:price => "$90")
206
+ prong.price.should == "$90"
207
+ prong.errors.should be_empty
208
+ prong.request_path.should == "/widgets/1/prongs/102"
209
+ end
210
+
211
+ it "should return the new prong object with errors when unsuccessful" do
212
+ stub_request(:post, "http://user:password@www.example.com/api/v0/widgets/1/prongs").
213
+ with(:headers => {'Accept'=>'application/json'}, :body => "prong[price]=%24100").
214
+ to_return(:status => 422, :body => { :errors => ["error 1", "error 2"] }.to_json, :headers => {})
215
+
216
+ prong = @widget.create_prong(:price => "$100")
217
+ prong.should be_a(Prong)
218
+ prong.errors.should == ["error 1", "error 2"]
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end