jira-ruby-dmg 0.1.10

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +46 -0
  6. data/README.rdoc +309 -0
  7. data/Rakefile +28 -0
  8. data/example.rb +119 -0
  9. data/http-basic-example.rb +112 -0
  10. data/jira-ruby-dmg.gemspec +28 -0
  11. data/lib/jira/base.rb +497 -0
  12. data/lib/jira/base_factory.rb +49 -0
  13. data/lib/jira/client.rb +165 -0
  14. data/lib/jira/has_many_proxy.rb +43 -0
  15. data/lib/jira/http_client.rb +69 -0
  16. data/lib/jira/http_error.rb +16 -0
  17. data/lib/jira/oauth_client.rb +84 -0
  18. data/lib/jira/railtie.rb +10 -0
  19. data/lib/jira/request_client.rb +18 -0
  20. data/lib/jira/resource/attachment.rb +12 -0
  21. data/lib/jira/resource/comment.rb +14 -0
  22. data/lib/jira/resource/component.rb +10 -0
  23. data/lib/jira/resource/field.rb +10 -0
  24. data/lib/jira/resource/filter.rb +15 -0
  25. data/lib/jira/resource/issue.rb +76 -0
  26. data/lib/jira/resource/issuetype.rb +10 -0
  27. data/lib/jira/resource/priority.rb +10 -0
  28. data/lib/jira/resource/project.rb +31 -0
  29. data/lib/jira/resource/status.rb +10 -0
  30. data/lib/jira/resource/transition.rb +33 -0
  31. data/lib/jira/resource/user.rb +14 -0
  32. data/lib/jira/resource/version.rb +10 -0
  33. data/lib/jira/resource/worklog.rb +16 -0
  34. data/lib/jira/tasks.rb +0 -0
  35. data/lib/jira/version.rb +3 -0
  36. data/lib/jira.rb +33 -0
  37. data/lib/tasks/generate.rake +18 -0
  38. data/spec/integration/attachment_spec.rb +23 -0
  39. data/spec/integration/comment_spec.rb +55 -0
  40. data/spec/integration/component_spec.rb +42 -0
  41. data/spec/integration/field_spec.rb +35 -0
  42. data/spec/integration/issue_spec.rb +94 -0
  43. data/spec/integration/issuetype_spec.rb +26 -0
  44. data/spec/integration/priority_spec.rb +27 -0
  45. data/spec/integration/project_spec.rb +56 -0
  46. data/spec/integration/status_spec.rb +27 -0
  47. data/spec/integration/transition_spec.rb +52 -0
  48. data/spec/integration/user_spec.rb +25 -0
  49. data/spec/integration/version_spec.rb +43 -0
  50. data/spec/integration/worklog_spec.rb +55 -0
  51. data/spec/jira/base_factory_spec.rb +46 -0
  52. data/spec/jira/base_spec.rb +586 -0
  53. data/spec/jira/client_spec.rb +188 -0
  54. data/spec/jira/has_many_proxy_spec.rb +45 -0
  55. data/spec/jira/http_client_spec.rb +109 -0
  56. data/spec/jira/http_error_spec.rb +25 -0
  57. data/spec/jira/oauth_client_spec.rb +111 -0
  58. data/spec/jira/request_client_spec.rb +14 -0
  59. data/spec/jira/resource/attachment_spec.rb +20 -0
  60. data/spec/jira/resource/filter_spec.rb +97 -0
  61. data/spec/jira/resource/issue_spec.rb +107 -0
  62. data/spec/jira/resource/project_factory_spec.rb +13 -0
  63. data/spec/jira/resource/project_spec.rb +70 -0
  64. data/spec/jira/resource/worklog_spec.rb +24 -0
  65. data/spec/mock_responses/attachment/10000.json +20 -0
  66. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  67. data/spec/mock_responses/component/10000.json +39 -0
  68. data/spec/mock_responses/component/10000.put.json +39 -0
  69. data/spec/mock_responses/component.post.json +28 -0
  70. data/spec/mock_responses/field/1.json +15 -0
  71. data/spec/mock_responses/field.json +32 -0
  72. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  73. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  74. data/spec/mock_responses/issue/10002/comment.json +65 -0
  75. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  76. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  77. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  78. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  79. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  80. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  81. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  82. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  83. data/spec/mock_responses/issue/10002.json +126 -0
  84. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  85. data/spec/mock_responses/issue.json +1108 -0
  86. data/spec/mock_responses/issue.post.json +5 -0
  87. data/spec/mock_responses/issuetype/5.json +8 -0
  88. data/spec/mock_responses/issuetype.json +42 -0
  89. data/spec/mock_responses/priority/1.json +8 -0
  90. data/spec/mock_responses/priority.json +42 -0
  91. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  92. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  93. data/spec/mock_responses/project.json +12 -0
  94. data/spec/mock_responses/status/1.json +7 -0
  95. data/spec/mock_responses/status.json +37 -0
  96. data/spec/mock_responses/user_username=admin.json +17 -0
  97. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  98. data/spec/mock_responses/version/10000.json +11 -0
  99. data/spec/mock_responses/version/10000.put.json +7 -0
  100. data/spec/mock_responses/version.post.json +7 -0
  101. data/spec/spec_helper.rb +22 -0
  102. data/spec/support/clients_helper.rb +16 -0
  103. data/spec/support/matchers/have_attributes.rb +11 -0
  104. data/spec/support/matchers/have_many.rb +9 -0
  105. data/spec/support/matchers/have_one.rb +5 -0
  106. data/spec/support/shared_examples/integration.rb +190 -0
  107. metadata +301 -0
@@ -0,0 +1,84 @@
1
+ {
2
+ "self": "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT",
3
+ "id": "10001",
4
+ "key": "SAMPLEPROJECT",
5
+ "lead": {
6
+ "self": "http://localhost:2990/jira/rest/api/2/user?username=admin",
7
+ "name": "admin",
8
+ "avatarUrls": {
9
+ "16x16": "http://localhost:2990/jira/secure/useravatar?size=small&avatarId=10122",
10
+ "48x48": "http://localhost:2990/jira/secure/useravatar?avatarId=10122"
11
+ },
12
+ "displayName": "admin",
13
+ "active": true
14
+ },
15
+ "components": [
16
+ {
17
+ "self": "http://localhost:2990/jira/rest/api/2/component/10001",
18
+ "id": "10001",
19
+ "name": "Jamflam",
20
+ "isAssigneeTypeValid": false
21
+ },
22
+ {
23
+ "self": "http://localhost:2990/jira/rest/api/2/component/10000",
24
+ "id": "10000",
25
+ "name": "Jammy",
26
+ "description": "Description!",
27
+ "isAssigneeTypeValid": false
28
+ }
29
+ ],
30
+ "issueTypes": [
31
+ {
32
+ "self": "http://localhost:2990/jira/rest/api/2/issuetype/1",
33
+ "id": "1",
34
+ "description": "A problem which impairs or prevents the functions of the product.",
35
+ "iconUrl": "http://localhost:2990/jira/images/icons/bug.gif",
36
+ "name": "Bug",
37
+ "subtask": false
38
+ },
39
+ {
40
+ "self": "http://localhost:2990/jira/rest/api/2/issuetype/2",
41
+ "id": "2",
42
+ "description": "A new feature of the product, which has yet to be developed.",
43
+ "iconUrl": "http://localhost:2990/jira/images/icons/newfeature.gif",
44
+ "name": "New Feature",
45
+ "subtask": false
46
+ },
47
+ {
48
+ "self": "http://localhost:2990/jira/rest/api/2/issuetype/3",
49
+ "id": "3",
50
+ "description": "A task that needs to be done.",
51
+ "iconUrl": "http://localhost:2990/jira/images/icons/task.gif",
52
+ "name": "Task",
53
+ "subtask": false
54
+ },
55
+ {
56
+ "self": "http://localhost:2990/jira/rest/api/2/issuetype/4",
57
+ "id": "4",
58
+ "description": "An improvement or enhancement to an existing feature or task.",
59
+ "iconUrl": "http://localhost:2990/jira/images/icons/improvement.gif",
60
+ "name": "Improvement",
61
+ "subtask": false
62
+ },
63
+ {
64
+ "self": "http://localhost:2990/jira/rest/api/2/issuetype/5",
65
+ "id": "5",
66
+ "description": "The sub-task of the issue",
67
+ "iconUrl": "http://localhost:2990/jira/images/icons/issue_subtask.gif",
68
+ "name": "Sub-task",
69
+ "subtask": true
70
+ }
71
+ ],
72
+ "assigneeType": "PROJECT_LEAD",
73
+ "versions": [],
74
+ "name": "Sample Project for Developing RoR RESTful API",
75
+ "roles": {
76
+ "Users": "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT/role/10000",
77
+ "Administrators": "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT/role/10002",
78
+ "Developers": "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT/role/10001"
79
+ },
80
+ "avatarUrls": {
81
+ "16x16": "http://localhost:2990/jira/secure/projectavatar?size=small&pid=10001&avatarId=10011",
82
+ "48x48": "http://localhost:2990/jira/secure/projectavatar?pid=10001&avatarId=10011"
83
+ }
84
+ }
@@ -0,0 +1,12 @@
1
+ [
2
+ {
3
+ "self": "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT",
4
+ "id": "10001",
5
+ "key": "SAMPLEPROJECT",
6
+ "name": "Sample Project for Developing RoR RESTful API",
7
+ "avatarUrls": {
8
+ "16x16": "http://localhost:2990/jira/secure/projectavatar?size=small&pid=10001&avatarId=10011",
9
+ "48x48": "http://localhost:2990/jira/secure/projectavatar?pid=10001&avatarId=10011"
10
+ }
11
+ }
12
+ ]
@@ -0,0 +1,7 @@
1
+ {
2
+ "self": "http://localhost:2990/jira/rest/api/2/status/1",
3
+ "description": "The issue is open and ready for the assignee to start work on it.",
4
+ "iconUrl": "http://localhost:2990/jira/images/icons/status_open.gif",
5
+ "name": "Open",
6
+ "id": "1"
7
+ }
@@ -0,0 +1,37 @@
1
+ [
2
+ {
3
+ "self": "http://localhost:2990/jira/rest/api/2/status/1",
4
+ "description": "The issue is open and ready for the assignee to start work on it.",
5
+ "iconUrl": "http://localhost:2990/jira/images/icons/status_open.gif",
6
+ "name": "Open",
7
+ "id": "1"
8
+ },
9
+ {
10
+ "self": "http://localhost:2990/jira/rest/api/2/status/3",
11
+ "description": "This issue is being actively worked on at the moment by the assignee.",
12
+ "iconUrl": "http://localhost:2990/jira/images/icons/status_inprogress.gif",
13
+ "name": "In Progress",
14
+ "id": "3"
15
+ },
16
+ {
17
+ "self": "http://localhost:2990/jira/rest/api/2/status/4",
18
+ "description": "This issue was once resolved, but the resolution was deemed incorrect. From here issues are either marked assigned or resolved.",
19
+ "iconUrl": "http://localhost:2990/jira/images/icons/status_reopened.gif",
20
+ "name": "Reopened",
21
+ "id": "4"
22
+ },
23
+ {
24
+ "self": "http://localhost:2990/jira/rest/api/2/status/5",
25
+ "description": "A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.",
26
+ "iconUrl": "http://localhost:2990/jira/images/icons/status_resolved.gif",
27
+ "name": "Resolved",
28
+ "id": "5"
29
+ },
30
+ {
31
+ "self": "http://localhost:2990/jira/rest/api/2/status/6",
32
+ "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.",
33
+ "iconUrl": "http://localhost:2990/jira/images/icons/status_closed.gif",
34
+ "name": "Closed",
35
+ "id": "6"
36
+ }
37
+ ]
@@ -0,0 +1,17 @@
1
+ {
2
+ "self": "http://localhost:2990/jira/rest/api/2/user?username=admin",
3
+ "name": "admin",
4
+ "emailAddress": "admin@example.com",
5
+ "avatarUrls": {
6
+ "16x16": "http://localhost:2990/jira/secure/useravatar?size=small&avatarId=10122",
7
+ "48x48": "http://localhost:2990/jira/secure/useravatar?avatarId=10122"
8
+ },
9
+ "displayName": "admin",
10
+ "active": true,
11
+ "timeZone": "Pacific/Auckland",
12
+ "groups": {
13
+ "size": 3,
14
+ "items": []
15
+ },
16
+ "expand": "groups"
17
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "errorMessages": [
3
+ "Unrecognized field \"chump\" (Class com.atlassian.jira.rest.v2.issue.version.VersionBean), not marked as ignorable\n at [Source: org.apache.catalina.connector.CoyoteInputStream@4264a42e; line: 1, column: 2]"
4
+ ]
5
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "self": "http://localhost:2990/jira/rest/api/2/version/10000",
3
+ "id": "10000",
4
+ "description": "Initial version",
5
+ "name": "1.0",
6
+ "overdue": false,
7
+ "userReleaseDate": "12/Jan/12",
8
+ "archived": false,
9
+ "releaseDate": "2012-01-12",
10
+ "released": false
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "self": "http://localhost:2990/jira/rest/api/2/version/10000",
3
+ "id": "10000",
4
+ "name": "2.0.0",
5
+ "archived": false,
6
+ "released": false
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "self": "http://localhost:2990/jira/rest/api/2/version/10001",
3
+ "id": "10001",
4
+ "name": "2.0",
5
+ "archived": false,
6
+ "released": false
7
+ }
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'webmock/rspec'
5
+ Dir["./spec/support/**/*.rb"].each {|f| require f}
6
+
7
+ require 'jira'
8
+
9
+ RSpec.configure do |config|
10
+ config.extend ClientsHelper
11
+ end
12
+
13
+
14
+ def get_mock_response(file, value_if_file_not_found = false)
15
+ begin
16
+ file.sub!('?', '_') # we have to replace this character on Windows machine
17
+ File.read(File.join(File.dirname(__FILE__), 'mock_responses/', file))
18
+ rescue Errno::ENOENT => e
19
+ raise e if value_if_file_not_found == false
20
+ value_if_file_not_found
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module ClientsHelper
2
+ def with_each_client
3
+ clients = {}
4
+
5
+ oauth_client = JIRA::Client.new({ :consumer_key => 'foo', :consumer_secret => 'bar' })
6
+ oauth_client.set_access_token('abc', '123')
7
+ clients["http://localhost:2990"] = oauth_client
8
+
9
+ basic_client = JIRA::Client.new({ :username => 'foo', :password => 'bar', :auth_type => :basic, :use_ssl => false })
10
+ clients["http://foo:bar@localhost:2990"] = basic_client
11
+
12
+ clients.each do |site_url, client|
13
+ yield site_url, client
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ RSpec::Matchers.define :have_attributes do |expected|
2
+ match do |actual|
3
+ expected.each do |key, value|
4
+ actual.attrs[key].should == value
5
+ end
6
+ end
7
+
8
+ failure_message_for_should do |actual|
9
+ "expected #{actual.attrs} to match #{expected}"
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ RSpec::Matchers.define :have_many do |collection, klass|
2
+ match do |actual|
3
+ actual.send(collection).class.should == JIRA::HasManyProxy
4
+ actual.send(collection).length.should > 0
5
+ actual.send(collection).each do |member|
6
+ member.class.should == klass
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ RSpec::Matchers.define :have_one do |resource, klass|
2
+ match do |actual|
3
+ actual.send(resource).class.should == klass
4
+ end
5
+ end
@@ -0,0 +1,190 @@
1
+ require 'cgi'
2
+
3
+ def get_mock_from_path(method, options = {})
4
+ if defined? belongs_to
5
+ prefix = belongs_to.path_component + '/'
6
+ else
7
+ prefix = ''
8
+ end
9
+
10
+ if options[:url]
11
+ url = options[:url]
12
+ elsif options[:key]
13
+ url = described_class.singular_path(client, options[:key], prefix)
14
+ else
15
+ url = described_class.collection_path(client, prefix)
16
+ end
17
+ file_path = url.sub(client.options[:rest_base_path], '')
18
+ file_path = file_path + '.' + options[:suffix] if options[:suffix]
19
+ file_path = file_path + '.' + method.to_s unless method == :get
20
+ value_if_not_found = options.keys.include?(:value_if_not_found) ? options[:value_if_not_found] : false
21
+ get_mock_response("#{file_path}.json", value_if_not_found)
22
+ end
23
+
24
+ def class_basename
25
+ described_class.name.split('::').last
26
+ end
27
+
28
+ def options
29
+ options = {}
30
+ if defined? belongs_to
31
+ options[belongs_to.to_sym] = belongs_to
32
+ end
33
+ options
34
+ end
35
+
36
+ def prefix
37
+ prefix = '/'
38
+ if defined? belongs_to
39
+ prefix = belongs_to.path_component + '/'
40
+ end
41
+ prefix
42
+ end
43
+
44
+ def build_receiver
45
+ if defined?(belongs_to)
46
+ belongs_to.send(described_class.endpoint_name.pluralize.to_sym)
47
+ else
48
+ client.send(class_basename)
49
+ end
50
+ end
51
+
52
+ shared_examples "a resource" do
53
+
54
+ it "gracefully handles non-json responses" do
55
+ if defined? target
56
+ subject = target
57
+ else
58
+ subject = client.send(class_basename).build(described_class.key_attribute.to_s => '99999')
59
+ end
60
+ stub_request(:put, site_url + subject.url).
61
+ to_return(:status => 405, :body => "<html><body>Some HTML</body></html>")
62
+ subject.save('foo' => 'bar').should be_false
63
+ lambda do
64
+ subject.save!('foo' => 'bar').should be_false
65
+ end.should raise_error(JIRA::HTTPError)
66
+ end
67
+
68
+ end
69
+
70
+ shared_examples "a resource with a collection GET endpoint" do
71
+
72
+ it "should get the collection" do
73
+ stub_request(:get, site_url + described_class.collection_path(client)).
74
+ to_return(:status => 200, :body => get_mock_from_path(:get))
75
+ collection = build_receiver.all
76
+ collection.length.should == expected_collection_length
77
+
78
+ first = collection.first
79
+ first.should have_attributes(expected_attributes)
80
+ end
81
+
82
+ end
83
+
84
+ shared_examples "a resource with JQL inputs and a collection GET endpoint" do
85
+
86
+ it "should get the collection" do
87
+ stub_request(:get, site_url + client.options[:rest_base_path] + '/search?jql=' + CGI.escape(jql_query_string)).
88
+ to_return(:status => 200, :body => get_mock_response('issue.json'))
89
+ collection = build_receiver.jql(jql_query_string)
90
+ collection.length.should == expected_collection_length
91
+
92
+ first = collection.first
93
+ first.should have_attributes(expected_attributes)
94
+ end
95
+
96
+ end
97
+
98
+ shared_examples "a resource with a singular GET endpoint" do
99
+
100
+ it "GETs a single resource" do
101
+ # E.g., for JIRA::Resource::Project, we need to call
102
+ # client.Project.find()
103
+ stub_request(:get, site_url + described_class.singular_path(client, key, prefix)).
104
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key => key))
105
+ subject = client.send(class_basename).find(key, options)
106
+
107
+ subject.should have_attributes(expected_attributes)
108
+ end
109
+
110
+ it "builds and fetches a single resource" do
111
+ # E.g., for JIRA::Resource::Project, we need to call
112
+ # client.Project.build('key' => 'ABC123')
113
+ stub_request(:get, site_url + described_class.singular_path(client, key, prefix)).
114
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key => key))
115
+
116
+ subject = build_receiver.build(described_class.key_attribute.to_s => key)
117
+ subject.fetch
118
+
119
+ subject.should have_attributes(expected_attributes)
120
+ end
121
+
122
+ it "handles a 404" do
123
+ stub_request(:get, site_url + described_class.singular_path(client, '99999', prefix)).
124
+ to_return(:status => 404, :body => '{"errorMessages":["'+class_basename+' Does Not Exist"],"errors": {}}')
125
+ lambda do
126
+ client.send(class_basename).find('99999', options)
127
+ end.should raise_exception(JIRA::HTTPError)
128
+ end
129
+ end
130
+
131
+ shared_examples "a resource with a DELETE endpoint" do
132
+ it "deletes a resource" do
133
+ # E.g., for JIRA::Resource::Project, we need to call
134
+ # client.Project.delete()
135
+ stub_request(:delete, site_url + described_class.singular_path(client, key, prefix)).
136
+ to_return(:status => 204, :body => nil)
137
+
138
+ subject = build_receiver.build(described_class.key_attribute.to_s => key)
139
+ subject.delete.should be_true
140
+ end
141
+ end
142
+
143
+ shared_examples "a resource with a POST endpoint" do
144
+
145
+ it "saves a new resource" do
146
+ stub_request(:post, site_url + described_class.collection_path(client, prefix)).
147
+ to_return(:status => 201, :body => get_mock_from_path(:post))
148
+ subject = build_receiver.build
149
+ subject.save(attributes_for_post).should be_true
150
+ expected_attributes_from_post.each do |method_name, value|
151
+ subject.send(method_name).should == value
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ shared_examples "a resource with a PUT endpoint" do
158
+
159
+ it "saves an existing component" do
160
+ stub_request(:get, site_url + described_class.singular_path(client, key, prefix)).
161
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key =>key))
162
+ stub_request(:put, site_url + described_class.singular_path(client, key, prefix)).
163
+ to_return(:status => 200, :body => get_mock_from_path(:put, :key => key, :value_if_not_found => nil))
164
+ subject = build_receiver.build(described_class.key_attribute.to_s => key)
165
+ subject.fetch
166
+ subject.save(attributes_for_put).should be_true
167
+ expected_attributes_from_put.each do |method_name, value|
168
+ subject.send(method_name).should == value
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ shared_examples 'a resource with a PUT endpoint that rejects invalid fields' do
175
+
176
+ it "fails to save with an invalid field" do
177
+ stub_request(:get, site_url + described_class.singular_path(client, key)).
178
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key => key))
179
+ stub_request(:put, site_url + described_class.singular_path(client, key)).
180
+ to_return(:status => 400, :body => get_mock_from_path(:put, :key => key, :suffix => "invalid"))
181
+ subject = client.send(class_basename).build(described_class.key_attribute.to_s => key)
182
+ subject.fetch
183
+
184
+ subject.save('fields'=> {'invalid' => 'field'}).should be_false
185
+ lambda do
186
+ subject.save!('fields'=> {'invalid' => 'field'})
187
+ end.should raise_error(JIRA::HTTPError)
188
+ end
189
+
190
+ end