mavenlink 0.0.1 → 0.2.0

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.
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