cherby 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/cherby.gemspec +1 -1
- data/lib/cherby/business_object.rb +60 -20
- data/lib/cherby/exceptions.rb +3 -0
- data/lib/cherby/incident.rb +3 -14
- data/lib/cherby/journal_note.rb +0 -4
- data/lib/cherby/task.rb +3 -5
- data/spec/business_object_spec.rb +38 -20
- data/spec/cherwell_spec.rb +36 -0
- metadata +2 -2
data/cherby.gemspec
CHANGED
@@ -3,22 +3,27 @@ require 'nokogiri'
|
|
3
3
|
module Cherby
|
4
4
|
# Cherwell BusinessObject wrapper, with data represented as an XML DOM
|
5
5
|
class BusinessObject
|
6
|
+
# Return the name of this class.
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
#
|
10
|
+
def self.object_type
|
11
|
+
return self.name.split('::').last
|
12
|
+
end
|
6
13
|
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
attr_accessor :object_name, :default_values
|
14
|
+
# Return the name of this instance's class.
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
#
|
18
|
+
def object_type
|
19
|
+
return self.class.object_type
|
14
20
|
end
|
15
21
|
|
16
22
|
# Create a new BusinessObject subclass instance from the given hash of
|
17
23
|
# 'FieldName' => 'Field Value'.
|
18
24
|
def self.create(fields={})
|
19
|
-
type_name = self.object_name
|
20
25
|
builder = Nokogiri::XML::Builder.new {
|
21
|
-
BusinessObject_('Name' =>
|
26
|
+
BusinessObject_('Name' => self.object_type, 'RecID' => 'TODO') {
|
22
27
|
FieldList_ {
|
23
28
|
fields.each { |name, value|
|
24
29
|
Field_(value, 'Name' => name)
|
@@ -35,6 +40,24 @@ module Cherby
|
|
35
40
|
attr_reader :dom
|
36
41
|
|
37
42
|
# Create a new BusinessObject instance from the given XML string.
|
43
|
+
#
|
44
|
+
# @param [String] xml
|
45
|
+
# XML for the BusinessObject to create. The XML must contain a
|
46
|
+
# `BusinessObject` element, with a `FieldList` element within it,
|
47
|
+
# containing zero or more `Field` elements, each having a `Name`
|
48
|
+
# attribute, and a string value. For example:
|
49
|
+
#
|
50
|
+
# <BusinessObject Name="Customer">
|
51
|
+
# <FieldList>
|
52
|
+
# <Field Name="FirstName">Steven</Field>
|
53
|
+
# <Field Name="LastName">Moffat</Field>
|
54
|
+
# [...]
|
55
|
+
# </FieldList>
|
56
|
+
# </BusinessObject>
|
57
|
+
#
|
58
|
+
# @raise [Cherby::BadFormat]
|
59
|
+
# If the XML DOM's structure is incorrect
|
60
|
+
#
|
38
61
|
def initialize(xml)
|
39
62
|
@dom = Nokogiri::XML(xml)
|
40
63
|
check_dom_format!
|
@@ -57,7 +80,11 @@ module Cherby
|
|
57
80
|
return true
|
58
81
|
end
|
59
82
|
|
60
|
-
# Return the XML representation of this BusinessObject
|
83
|
+
# Return the XML representation of this BusinessObject.
|
84
|
+
#
|
85
|
+
# @return [String]
|
86
|
+
# The BusinessObject's XML representation
|
87
|
+
#
|
61
88
|
def to_xml
|
62
89
|
return @dom.to_xml
|
63
90
|
end
|
@@ -71,6 +98,9 @@ module Cherby
|
|
71
98
|
# The node for the `Field` element, or `nil` if no Field
|
72
99
|
# with the given `Name` exists.
|
73
100
|
#
|
101
|
+
# @raise [Cherby::BadFormat]
|
102
|
+
# If the XML DOM's structure is incorrect
|
103
|
+
#
|
74
104
|
def get_field_node(field_name)
|
75
105
|
check_dom_format!
|
76
106
|
selector = "BusinessObject > FieldList > Field[@Name=#{field_name}]"
|
@@ -119,12 +149,12 @@ module Cherby
|
|
119
149
|
# The date/time string to parse. May or may not include a trailing
|
120
150
|
# [+-]HH:MM or [+-]HHMM.
|
121
151
|
#
|
122
|
-
# @param [Integer]
|
152
|
+
# @param [Integer] default_tz_offset
|
123
153
|
# Offset in hours (positive or negative) between UTC and the given
|
124
|
-
# `dt_string`. For example, Eastern Time is `-5`. This is ONLY used if
|
154
|
+
# `dt_string`. For example, US Eastern Time is `-5`. This is ONLY used if
|
125
155
|
# `dt_string` does NOT include a trailing offset component.
|
126
156
|
#
|
127
|
-
def self.parse_datetime(dt_string,
|
157
|
+
def self.parse_datetime(dt_string, default_tz_offset=0)
|
128
158
|
begin
|
129
159
|
result = DateTime.parse(dt_string)
|
130
160
|
rescue
|
@@ -133,27 +163,37 @@ module Cherby
|
|
133
163
|
# If offset was part of the dt_string, use new_offset to get UTC
|
134
164
|
if dt_string =~ /[+-]\d\d:?\d\d$/
|
135
165
|
return result.new_offset(0)
|
136
|
-
# Otherwise, subtract the
|
166
|
+
# Otherwise, subtract the default offset to get UTC time
|
137
167
|
else
|
138
|
-
return result - Rational(
|
168
|
+
return result - Rational(default_tz_offset.to_i, 24)
|
139
169
|
end
|
140
170
|
end
|
141
171
|
|
142
172
|
# Return the last-modified date/time of this BusinessObject
|
143
|
-
# (LastModDateTime converted to DateTime)
|
144
|
-
|
173
|
+
# (LastModDateTime converted to DateTime).
|
174
|
+
#
|
175
|
+
# @param [Integer] default_tz_offset
|
176
|
+
# Offset in hours (positive or negative) between UTC and the given
|
177
|
+
# `dt_string`. For example, US Eastern Time is `-5`. This is ONLY used if
|
178
|
+
# `dt_string` does NOT include a trailing offset component.
|
179
|
+
#
|
180
|
+
# @raise [Cherby::MissingData, Cherby::BadFormat]
|
181
|
+
# If `LastModDateTime` is missing or cannot be parsed, respectively
|
182
|
+
#
|
183
|
+
def modified(default_tz_offset=0)
|
145
184
|
last_mod = self['LastModDateTime']
|
146
185
|
if last_mod.nil? || last_mod.empty?
|
147
|
-
raise
|
186
|
+
raise Cherby::MissingData.new("BusinessObject is missing LastModDateTime field.")
|
148
187
|
end
|
149
188
|
begin
|
150
|
-
return BusinessObject.parse_datetime(last_mod)
|
189
|
+
return BusinessObject.parse_datetime(last_mod, default_tz_offset)
|
151
190
|
rescue(ArgumentError)
|
152
|
-
raise
|
191
|
+
raise Cherby::BadFormat.new("Cannot parse LastModDateTime: '#{last_mod}'")
|
153
192
|
end
|
154
193
|
end
|
155
194
|
|
156
195
|
# Return the last-modified time as a human-readable string
|
196
|
+
# TODO: Make this more error-tolerant with a default date?
|
157
197
|
def mod_s
|
158
198
|
return modified.strftime('%Y-%m-%d %H:%M:%S')
|
159
199
|
end
|
data/lib/cherby/exceptions.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module Cherby
|
2
|
+
# Base class for all Cherby custom exceptions
|
2
3
|
class CherbyError < RuntimeError; end
|
4
|
+
|
3
5
|
class LoginFailed < CherbyError; end
|
4
6
|
class NotFound < CherbyError; end
|
7
|
+
class MissingData < CherbyError; end
|
5
8
|
class BadFormat < CherbyError; end
|
6
9
|
|
7
10
|
class SoapError < CherbyError
|
data/lib/cherby/incident.rb
CHANGED
@@ -6,18 +6,7 @@ require 'cherby/journal_note'
|
|
6
6
|
module Cherby
|
7
7
|
# Wrapper for Cherwell incident objects.
|
8
8
|
class Incident < BusinessObject
|
9
|
-
|
10
|
-
# FIXME: Rename these and make use of them?
|
11
|
-
@default_values = {
|
12
|
-
:service => "Auto Generated",
|
13
|
-
:service_group => "Auto Generated",
|
14
|
-
:category => "Auto Generated",
|
15
|
-
:sub_category => "JIRA",
|
16
|
-
:impact => "Inconvenience",
|
17
|
-
:urgency => "Medium",
|
18
|
-
:priority => "3",
|
19
|
-
}
|
20
|
-
|
9
|
+
# Return the Incident's public ID
|
21
10
|
def id
|
22
11
|
self['IncidentID']
|
23
12
|
end
|
@@ -46,7 +35,7 @@ module Cherby
|
|
46
35
|
self["SubcategoryNonHR"] = "JIRA"
|
47
36
|
end
|
48
37
|
|
49
|
-
# Return Task instances for all tasks associated with this Incident
|
38
|
+
# Return Task instances for all tasks associated with this Incident.
|
50
39
|
#
|
51
40
|
# @return [Array<Task>]
|
52
41
|
#
|
@@ -56,7 +45,7 @@ module Cherby
|
|
56
45
|
end
|
57
46
|
end
|
58
47
|
|
59
|
-
# Return all
|
48
|
+
# Return all JournalNotes associated with this Incident.
|
60
49
|
#
|
61
50
|
# @return [Array<JournalNote>]
|
62
51
|
#
|
data/lib/cherby/journal_note.rb
CHANGED
data/lib/cherby/task.rb
CHANGED
@@ -2,16 +2,14 @@ require 'date'
|
|
2
2
|
require 'cherby/business_object'
|
3
3
|
|
4
4
|
module Cherby
|
5
|
+
# Wrapper for Cherwell task objects.
|
5
6
|
class Task < BusinessObject
|
6
|
-
|
7
|
-
@default_values = {
|
8
|
-
:status => "New",
|
9
|
-
}
|
10
|
-
|
7
|
+
# Return this task's public ID.
|
11
8
|
def id
|
12
9
|
self['TaskID']
|
13
10
|
end
|
14
11
|
|
12
|
+
# Return true if this task exists in Cherwell.
|
15
13
|
def exists?
|
16
14
|
return !id.to_s.empty?
|
17
15
|
end
|
@@ -1,25 +1,24 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
require 'cherby/business_object'
|
3
|
+
require 'cherby/exceptions'
|
3
4
|
|
4
5
|
# Some BusinessObject subclasses to test with
|
5
|
-
class MySubclass < Cherby::BusinessObject
|
6
|
-
@object_name = 'MySubclass'
|
7
|
-
@default_values = {}
|
8
|
-
end
|
9
|
-
|
10
|
-
class MySubclassNoTemplate < Cherby::BusinessObject
|
11
|
-
@object_name = 'MySubclassNoTemplate'
|
12
|
-
@default_values = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
class MySubclassNoTemplateFile < Cherby::BusinessObject
|
16
|
-
@object_name = 'MySubclassNoTemplateFile'
|
17
|
-
@default_values = {}
|
18
|
-
end
|
6
|
+
class MySubclass < Cherby::BusinessObject; end
|
19
7
|
|
20
8
|
describe Cherby::BusinessObject do
|
21
9
|
context "Class methods" do
|
10
|
+
describe "#object_type" do
|
11
|
+
it "returns the plain class name as a string" do
|
12
|
+
MySubclass.object_type.should == 'MySubclass'
|
13
|
+
end
|
14
|
+
end #object_type
|
15
|
+
|
22
16
|
describe "#create" do
|
17
|
+
it "uses object_type as the BusinessObject `Name` attribute" do
|
18
|
+
obj = MySubclass.create({})
|
19
|
+
obj.dom.css("BusinessObject").first.attr('Name').should == 'MySubclass'
|
20
|
+
end
|
21
|
+
|
23
22
|
it "sets options in the DOM" do
|
24
23
|
obj = MySubclass.create({'First' => 'Eric', 'Last' => 'Idle'})
|
25
24
|
first_name = obj.dom.css("BusinessObject[@Name=MySubclass] Field[@Name=First]").first
|
@@ -58,6 +57,13 @@ describe Cherby::BusinessObject do
|
|
58
57
|
|
59
58
|
|
60
59
|
context "Instance methods" do
|
60
|
+
describe "#object_type" do
|
61
|
+
it "returns the plain class name as a string" do
|
62
|
+
obj = MySubclass.create({})
|
63
|
+
obj.object_type.should == 'MySubclass'
|
64
|
+
end
|
65
|
+
end #object_type
|
66
|
+
|
61
67
|
describe "#initialize" do
|
62
68
|
it "accepts an XML string" do
|
63
69
|
xml = %Q{
|
@@ -75,7 +81,7 @@ describe Cherby::BusinessObject do
|
|
75
81
|
last_name.content.should == "Idle"
|
76
82
|
end
|
77
83
|
|
78
|
-
it "raises BadFormat if XML is missing FieldList" do
|
84
|
+
it "raises Cherby::BadFormat if XML is missing FieldList" do
|
79
85
|
xml = %Q{
|
80
86
|
<BusinessObject Name="MySubclass">
|
81
87
|
<Whatever/>
|
@@ -87,7 +93,7 @@ describe Cherby::BusinessObject do
|
|
87
93
|
Cherby::BadFormat, /missing 'BusinessObject > FieldList'/)
|
88
94
|
end
|
89
95
|
|
90
|
-
it "raises BadFormat if XML is missing BusinessObject" do
|
96
|
+
it "raises Cherby::BadFormat if XML is missing BusinessObject" do
|
91
97
|
xml = %Q{
|
92
98
|
<Whatever Name="MySubclass"/>
|
93
99
|
}
|
@@ -132,7 +138,7 @@ describe Cherby::BusinessObject do
|
|
132
138
|
end #[]=
|
133
139
|
|
134
140
|
describe "#modified" do
|
135
|
-
it "
|
141
|
+
it "returns the parsed LastModDateTime if it's a valid date/time string" do
|
136
142
|
# Parse the string version of now in order to truncate extra precision
|
137
143
|
# (otherwise the date won't compare equal later)
|
138
144
|
now = DateTime.parse(DateTime.now.to_s)
|
@@ -141,14 +147,26 @@ describe Cherby::BusinessObject do
|
|
141
147
|
obj.modified.should == now
|
142
148
|
end
|
143
149
|
|
144
|
-
it "
|
150
|
+
it "adjusts by default_tz_offset if LastModDateTime does not include an offset" do
|
151
|
+
# Current time, without timezone offset
|
152
|
+
now_str = DateTime.now.strftime('%Y-%m-%dT%H:%M:%S')
|
153
|
+
now = DateTime.parse(now_str)
|
154
|
+
|
155
|
+
obj = MySubclass.create({'LastModDateTime' => now_str})
|
156
|
+
# Test a bunch of different timezone offsets
|
157
|
+
[-7, -5, -2, 0, 1, 3, 4].each do |offset|
|
158
|
+
obj.modified(offset).should == now - Rational(offset, 24)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "raises Cherby::BadFormat if LastModDateTime value is invalid" do
|
145
163
|
obj = MySubclass.create({'LastModDateTime' => 'bogus'})
|
146
164
|
lambda do
|
147
165
|
obj.modified
|
148
|
-
end.should raise_error(
|
166
|
+
end.should raise_error(Cherby::BadFormat, /Cannot parse LastModDateTime: 'bogus'/)
|
149
167
|
end
|
150
168
|
|
151
|
-
it "
|
169
|
+
it "raises Cherby::MissingData if LastModDateTime is empty" do
|
152
170
|
xml = %Q{
|
153
171
|
<BusinessObject Name="MySubclass">
|
154
172
|
<FieldList>
|
data/spec/cherwell_spec.rb
CHANGED
@@ -172,8 +172,44 @@ describe Cherby::Cherwell do
|
|
172
172
|
result = @cherwell.get_object_xml(@name, @public_id)
|
173
173
|
result.should == xml
|
174
174
|
end
|
175
|
+
|
176
|
+
it "raises Cherby::SoapError if Savon::SOAPFault occurs" do
|
177
|
+
soap_fault = Savon::SOAPFault.new('fake-http', 'fake-nori')
|
178
|
+
@cherwell.client.stub(:call_wrap).and_raise(soap_fault)
|
179
|
+
lambda do
|
180
|
+
@cherwell.get_object_xml(@name, @public_id)
|
181
|
+
end.should raise_error { |ex|
|
182
|
+
ex.should be_a(Cherby::SoapError)
|
183
|
+
ex.message.should =~ /SOAPFault from method/
|
184
|
+
ex.http.should == 'fake-http'
|
185
|
+
}
|
186
|
+
end
|
175
187
|
end #get_object_xml
|
176
188
|
|
189
|
+
describe "#get_business_object" do
|
190
|
+
before(:each) do
|
191
|
+
@name = 'Thing'
|
192
|
+
@rec_id = '12345678901234567890123456789012'
|
193
|
+
@public_id = '12345'
|
194
|
+
@business_object_xml = %Q{
|
195
|
+
<BusinessObject>
|
196
|
+
<FieldList>
|
197
|
+
<Field Name="Name">#{@name}</Field>
|
198
|
+
<Field Name="RecID">#{@rec_id}</Field>
|
199
|
+
</FieldList>
|
200
|
+
</BusinessObject>
|
201
|
+
}
|
202
|
+
@cherwell.stub(:get_object_xml).
|
203
|
+
with(@name, @public_id).
|
204
|
+
and_return(@business_object_xml)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "returns a BusinessObject instance" do
|
208
|
+
result = @cherwell.get_business_object(@name, @public_id)
|
209
|
+
result.should be_a(Cherby::BusinessObject)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
177
213
|
describe "#update_object_xml" do
|
178
214
|
before(:each) do
|
179
215
|
@public_id = '80909'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cherby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-04-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: httpclient
|