cherby 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "cherby"
3
- s.version = "0.0.3"
3
+ s.version = "0.0.4"
4
4
  s.summary = "Cherwell-Ruby bridge"
5
5
  s.description = <<-EOS
6
6
  Cherby is a Ruby wrapper for the Cherwell Web Service.
@@ -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
- # Override this with the value of the BusinessObject's 'Name' attribute
8
- @object_name = ''
9
- # Fill this with default values for new instances of your BusinessObject
10
- @default_values = {}
11
-
12
- class << self
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' => type_name, 'RecID' => 'TODO') {
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] tz_offset
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, tz_offset=-5)
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 numeric offset to get UTC time
166
+ # Otherwise, subtract the default offset to get UTC time
137
167
  else
138
- return result - Rational(tz_offset.to_i, 24)
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
- def modified
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 RuntimeError, "BusinessObject is missing LastModDateTime field."
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 RuntimeError, "Cannot parse LastModDateTime: '#{last_mod}'"
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
@@ -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
@@ -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
- @object_name = 'Incident'
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 journal notes associated with this Incident.
48
+ # Return all JournalNotes associated with this Incident.
60
49
  #
61
50
  # @return [Array<JournalNote>]
62
51
  #
@@ -3,10 +3,6 @@ require 'cherby/business_object'
3
3
 
4
4
  module Cherby
5
5
  class JournalNote < BusinessObject
6
- @object_name = 'JournalNote'
7
- @default_values = {
8
- :details => "",
9
- }
10
6
  end
11
7
  end
12
8
 
@@ -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
- @object_name = 'Task'
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 "BusinessObject with a valid LastModDateTime value" do
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 "BusinessObject with an invalid LastModDateTime value" do
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(RuntimeError, /Cannot parse LastModDateTime: 'bogus'/)
166
+ end.should raise_error(Cherby::BadFormat, /Cannot parse LastModDateTime: 'bogus'/)
149
167
  end
150
168
 
151
- it "BusinessObject without a LastModDateTime field" do
169
+ it "raises Cherby::MissingData if LastModDateTime is empty" do
152
170
  xml = %Q{
153
171
  <BusinessObject Name="MySubclass">
154
172
  <FieldList>
@@ -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.3
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-03-31 00:00:00.000000000 Z
12
+ date: 2014-04-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httpclient