sclemmer-jira-ruby 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
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/lib/jira.rb +33 -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 +80 -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/tasks/generate.rake +18 -0
  37. data/sclemmer-jira-ruby.gemspec +28 -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 +583 -0
  53. data/spec/jira/client_spec.rb +188 -0
  54. data/spec/jira/has_many_proxy_spec.rb +47 -0
  55. data/spec/jira/http_client_spec.rb +109 -0
  56. data/spec/jira/http_error_spec.rb +26 -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 +123 -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.post.json +28 -0
  67. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  68. data/spec/mock_responses/component/10000.json +39 -0
  69. data/spec/mock_responses/component/10000.put.json +39 -0
  70. data/spec/mock_responses/field.json +32 -0
  71. data/spec/mock_responses/field/1.json +15 -0
  72. data/spec/mock_responses/issue.json +1108 -0
  73. data/spec/mock_responses/issue.post.json +5 -0
  74. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  75. data/spec/mock_responses/issue/10002.json +126 -0
  76. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  77. data/spec/mock_responses/issue/10002/comment.json +65 -0
  78. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  79. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  80. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  81. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  82. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  83. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  84. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  85. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  86. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  87. data/spec/mock_responses/issuetype.json +42 -0
  88. data/spec/mock_responses/issuetype/5.json +8 -0
  89. data/spec/mock_responses/priority.json +42 -0
  90. data/spec/mock_responses/priority/1.json +8 -0
  91. data/spec/mock_responses/project.json +12 -0
  92. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  93. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  94. data/spec/mock_responses/status.json +37 -0
  95. data/spec/mock_responses/status/1.json +7 -0
  96. data/spec/mock_responses/user_username=admin.json +17 -0
  97. data/spec/mock_responses/version.post.json +7 -0
  98. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  99. data/spec/mock_responses/version/10000.json +11 -0
  100. data/spec/mock_responses/version/10000.put.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 +194 -0
  107. metadata +302 -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,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,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,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,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,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,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
+ expect(actual.attrs[key]).to eq(value)
5
+ end
6
+ end
7
+
8
+ failure_message 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
+ expect(actual.send(collection).class).to eq(JIRA::HasManyProxy)
4
+ expect(actual.send(collection).length).to be > 0
5
+ actual.send(collection).each do |member|
6
+ expect(member.class).to eq(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
+ expect(actual.send(resource).class).to eq(klass)
4
+ end
5
+ end
@@ -0,0 +1,194 @@
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
+ expect(subject.save('foo' => 'bar')).to be_falsey
63
+ expect(lambda do
64
+ expect(subject.save!('foo' => 'bar')).to be_falsey
65
+ end).to 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
+
77
+ expect(collection.length).to eq(expected_collection_length)
78
+ expect(collection.first).to have_attributes(expected_attributes)
79
+ end
80
+
81
+ end
82
+
83
+ shared_examples "a resource with JQL inputs and a collection GET endpoint" do
84
+
85
+ it "should get the collection" do
86
+ stub_request(
87
+ :get,
88
+ site_url +
89
+ client.options[:rest_base_path] +
90
+ '/search?jql=' +
91
+ CGI.escape(jql_query_string)
92
+ ).to_return(:status => 200, :body => get_mock_response('issue.json'))
93
+
94
+ collection = build_receiver.jql(jql_query_string)
95
+
96
+ expect(collection.length).to eq(expected_collection_length)
97
+ expect(collection.first).to have_attributes(expected_attributes)
98
+ end
99
+
100
+ end
101
+
102
+ shared_examples "a resource with a singular GET endpoint" do
103
+
104
+ it "GETs a single resource" do
105
+ # E.g., for JIRA::Resource::Project, we need to call
106
+ # client.Project.find()
107
+ stub_request(:get, site_url + described_class.singular_path(client, key, prefix)).
108
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key => key))
109
+ subject = client.send(class_basename).find(key, options)
110
+
111
+ expect(subject).to have_attributes(expected_attributes)
112
+ end
113
+
114
+ it "builds and fetches a single resource" do
115
+ # E.g., for JIRA::Resource::Project, we need to call
116
+ # client.Project.build('key' => 'ABC123')
117
+ stub_request(:get, site_url + described_class.singular_path(client, key, prefix)).
118
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key => key))
119
+
120
+ subject = build_receiver.build(described_class.key_attribute.to_s => key)
121
+ subject.fetch
122
+
123
+ expect(subject).to have_attributes(expected_attributes)
124
+ end
125
+
126
+ it "handles a 404" do
127
+ stub_request(:get, site_url + described_class.singular_path(client, '99999', prefix)).
128
+ to_return(:status => 404, :body => '{"errorMessages":["'+class_basename+' Does Not Exist"],"errors": {}}')
129
+ expect( lambda do
130
+ client.send(class_basename).find('99999', options)
131
+ end).to raise_exception(JIRA::HTTPError)
132
+ end
133
+ end
134
+
135
+ shared_examples "a resource with a DELETE endpoint" do
136
+ it "deletes a resource" do
137
+ # E.g., for JIRA::Resource::Project, we need to call
138
+ # client.Project.delete()
139
+ stub_request(:delete, site_url + described_class.singular_path(client, key, prefix)).
140
+ to_return(:status => 204, :body => nil)
141
+
142
+ subject = build_receiver.build(described_class.key_attribute.to_s => key)
143
+ expect(subject.delete).to be_truthy
144
+ end
145
+ end
146
+
147
+ shared_examples "a resource with a POST endpoint" do
148
+
149
+ it "saves a new resource" do
150
+ stub_request(:post, site_url + described_class.collection_path(client, prefix)).
151
+ to_return(:status => 201, :body => get_mock_from_path(:post))
152
+ subject = build_receiver.build
153
+ expect(subject.save(attributes_for_post)).to be_truthy
154
+ expected_attributes_from_post.each do |method_name, value|
155
+ expect(subject.send(method_name)).to eq(value)
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+ shared_examples "a resource with a PUT endpoint" do
162
+
163
+ it "saves an existing component" do
164
+ stub_request(:get, site_url + described_class.singular_path(client, key, prefix)).
165
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key =>key))
166
+ stub_request(:put, site_url + described_class.singular_path(client, key, prefix)).
167
+ to_return(:status => 200, :body => get_mock_from_path(:put, :key => key, :value_if_not_found => nil))
168
+ subject = build_receiver.build(described_class.key_attribute.to_s => key)
169
+ subject.fetch
170
+ expect(subject.save(attributes_for_put)).to be_truthy
171
+ expected_attributes_from_put.each do |method_name, value|
172
+ expect(subject.send(method_name)).to eq(value)
173
+ end
174
+ end
175
+
176
+ end
177
+
178
+ shared_examples 'a resource with a PUT endpoint that rejects invalid fields' do
179
+
180
+ it "fails to save with an invalid field" do
181
+ stub_request(:get, site_url + described_class.singular_path(client, key)).
182
+ to_return(:status => 200, :body => get_mock_from_path(:get, :key => key))
183
+ stub_request(:put, site_url + described_class.singular_path(client, key)).
184
+ to_return(:status => 400, :body => get_mock_from_path(:put, :key => key, :suffix => "invalid"))
185
+ subject = client.send(class_basename).build(described_class.key_attribute.to_s => key)
186
+ subject.fetch
187
+
188
+ expect(subject.save('fields'=> {'invalid' => 'field'})).to be_falsey
189
+ expect(lambda do
190
+ subject.save!('fields'=> {'invalid' => 'field'})
191
+ end).to raise_error(JIRA::HTTPError)
192
+ end
193
+
194
+ end