dynamics_crm 0.7.1 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 511b29b2b863fee746767bb6bf4781d043f13969
4
- data.tar.gz: 9bffa8007f6f12332be94fb81e447b05c43c88e8
3
+ metadata.gz: 98498eb29f5847c9e344859c2a728367d4e5227a
4
+ data.tar.gz: a6621ceb97f27de1298c7afba4b99cfd6f5fa44b
5
5
  SHA512:
6
- metadata.gz: d093920089243f8f13e586cb5815c5b4ff2ddaffa504bd5e41e9de1f9c4e15a3356126e47887373bac5fe29d2b837432c299d97350c3afdd2b938fb3edf28499
7
- data.tar.gz: 45c230a953e78736440accdea82c61d77c440b1c5efdd7779a40131d11e9ea91e025ea2a0092889de1dcf8f2b49ab24dbeea483a5fce455079177d075b35e156
6
+ metadata.gz: 1773ffbe9669459911e9c08bc44f241fa825ca14858f738084e7179cb5b46b965b8c01222c3c6c0b98651da0da0dbdd3f59d52c4cfe304197950e9473379b666
7
+ data.tar.gz: 261c9ae185cfdd0151ff118a56f78bb873ca69ef866dfbee46b9bc4e85265292c1cd1d5dad7ef1b18c143668fb9798938bf90089d92cb4dae3bb37d7a071e4a9
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .idea
6
7
  Gemfile.lock
7
8
  InstalledFiles
8
9
  _yardoc
@@ -16,3 +17,4 @@ test/tmp
16
17
  test/version_tmp
17
18
  tmp
18
19
  vendor
20
+
data/.travis.yml CHANGED
@@ -1,7 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
4
3
  - 2.1.0
4
+ - 2.2.0
5
+ - 2.3.0
5
6
  notifications:
6
7
  email:
7
8
  recipients:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.8.0 (January 4, 2017)
2
+ * Support ArrayOfEntity so one can create bound entities like ActivityParties on create #41
3
+ * Add special case for nil value #43
4
+ * Add support for double type #44
5
+ * Support Or queries in Criteria #53
6
+ * Removed dependency on Curb #54
7
+ * Adds PageInfo support to QueryExpression. #56
8
+
1
9
  ## 0.7.0 (March 4, 2016)
2
10
  * Add EntityCollection to xml attributes #24
3
11
  * Fix illegal XML characters #25
data/README.md CHANGED
@@ -41,8 +41,33 @@ client.retrieve('account', '53291AAB-4A9A-E311-B097-6C3BE5A8DD60')
41
41
  client.retrieve_multiple('account', [["name", "Equal", "Test Account"]])
42
42
  # => [#<DynamicsCRM::XML::Entity ... >]
43
43
 
44
- client.retrieve_multiple('account', [["name", "Equal", "Test Account"], ["Name, "CreatedBy"]])
44
+ client.retrieve_multiple('account', [["name", "Equal", "Test Account"], ['salesstage', 'In', [0, 1, 2]]])
45
45
  # => [#<DynamicsCRM::XML::Entity ... >]
46
+
47
+ client.retrieve_multiple('account', [["telephone1", "EndsWith", "5558675309"], ["mobilephone", "EndsWith", "5558675309"]], [], "Or")
48
+ # => [#<DynamicsCRM::XML::Entity ... >]
49
+ ```
50
+
51
+ ### retrieve_multiple using QueryExpression
52
+
53
+ ```ruby
54
+ # Build QueryExpression
55
+ query = DynamicsCRM::XML::QueryExpression.new('account')
56
+ query.columns = %w(accountid name)
57
+ query.criteria.add_condition('name', 'NotEqual', 'Test Account')
58
+ # Optional PageInfo
59
+ query.page_info = DynamicsCRM::XML::PageInfo.new(count: 5, page_number: 1, return_total_record_count: true)
60
+
61
+ # Get first page
62
+ result = client.retrieve_multiple(query)
63
+
64
+ while result.MoreRecords
65
+ # Next page
66
+ query.page_info.page_number += 1
67
+ query.page_info.paging_cookie = result.PagingCookie
68
+
69
+ result = client.retrieve_multiple(query)
70
+ end
46
71
  ```
47
72
 
48
73
  ### fetch (FetchXml)
data/dynamics_crm.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = DynamicsCRM::VERSION
9
9
  spec.authors = ["Joe Heth"]
10
10
  spec.email = ["joeheth@gmail.com"]
11
- spec.description = %q{Ruby API for integrating with MS Dynamics 2011/2013 SOAP API}
12
- spec.summary = %q{Ruby gem for integrating with MS Dynamics 2011/2013 SOAP API}
11
+ spec.description = %q{Ruby API for integrating with MS Dynamics SOAP API}
12
+ spec.summary = %q{Ruby gem for integrating with MS Dynamics SOAP API}
13
13
  spec.homepage = "https://github.com/TinderBox/dynamics_crm"
14
14
  spec.license = "MIT"
15
15
 
@@ -18,7 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency 'curb', '>= 0.8', '< 1.0.0'
22
21
  spec.add_runtime_dependency 'mimemagic', '>= 0.2', '< 4.0.0'
23
22
  spec.add_runtime_dependency 'builder', '>= 3.0.0', '< 4.0.0'
24
23
 
data/lib/dynamics_crm.rb CHANGED
@@ -4,14 +4,16 @@ require "dynamics_crm/xml/message_builder"
4
4
  require 'dynamics_crm/xml/message_parser'
5
5
  require "dynamics_crm/xml/fault"
6
6
  require "dynamics_crm/xml/attributes"
7
+ require "dynamics_crm/xml/condition_expression"
7
8
  require "dynamics_crm/xml/column_set"
8
9
  require "dynamics_crm/xml/criteria"
9
- require "dynamics_crm/xml/query"
10
+ require "dynamics_crm/xml/query_expression"
10
11
  require "dynamics_crm/xml/fetch_expression"
11
12
  require "dynamics_crm/xml/entity"
12
13
  require "dynamics_crm/xml/entity_reference"
13
14
  require "dynamics_crm/xml/entity_collection"
14
15
  require "dynamics_crm/xml/money"
16
+ require "dynamics_crm/xml/page_info"
15
17
  require "dynamics_crm/response/result"
16
18
  require "dynamics_crm/response/retrieve_result"
17
19
  require "dynamics_crm/response/retrieve_multiple_result"
@@ -31,6 +33,7 @@ require "dynamics_crm/metadata/retrieve_all_entities_response"
31
33
  require "dynamics_crm/metadata/retrieve_entity_response"
32
34
  require "dynamics_crm/metadata/retrieve_attribute_response"
33
35
  require "dynamics_crm/metadata/retrieve_metadata_changes_response"
36
+ require "dynamics_crm/metadata/double"
34
37
  # Model
35
38
  require "dynamics_crm/model/entity"
36
39
  require "dynamics_crm/model/opportunity"
@@ -44,8 +47,8 @@ require "dynamics_crm/client"
44
47
  require 'bigdecimal'
45
48
  require 'base64'
46
49
  require "rexml/document"
50
+ require 'net/https'
47
51
  require 'mimemagic'
48
- require 'curl'
49
52
  require 'securerandom'
50
53
  require 'date'
51
54
  require 'cgi'
@@ -141,15 +141,19 @@ module DynamicsCRM
141
141
  })
142
142
  end
143
143
 
144
- def retrieve_multiple(entity_name, criteria=[], columns=[])
145
-
146
- query = XML::Query.new(entity_name)
147
- query.columns = columns
148
- query.criteria = XML::Criteria.new(criteria)
144
+ # Suports parameter list or QueryExpression object.
145
+ def retrieve_multiple(entity_name, criteria = [], columns = [], operator = nil)
146
+ if entity_name.is_a?(XML::QueryExpression)
147
+ query = entity_name
148
+ else
149
+ query = XML::QueryExpression.new(entity_name)
150
+ query.columns = columns
151
+ query.criteria = XML::Criteria.new(criteria, filter_operator: operator)
152
+ end
149
153
 
150
154
  request = retrieve_multiple_request(query)
151
155
  xml_response = post(organization_endpoint, request)
152
- return Response::RetrieveMultipleResult.new(xml_response)
156
+ Response::RetrieveMultipleResult.new(xml_response)
153
157
  end
154
158
 
155
159
  def fetch(fetchxml)
@@ -321,30 +325,29 @@ module DynamicsCRM
321
325
 
322
326
  def post(url, request)
323
327
  log_xml("REQUEST", request)
328
+ uri = URI.parse(url)
324
329
 
325
- c = Curl::Easy.new(url) do |http|
326
- # Set up headers.
327
- http.headers["Connection"] = "Keep-Alive"
328
- http.headers["Content-type"] = "application/soap+xml; charset=UTF-8"
329
- http.headers["Content-length"] = request.bytesize
330
-
331
- http.ssl_verify_peer = false
332
- http.timeout = timeout
333
- http.follow_location = true
334
- http.ssl_version = 1
335
- # http.verbose = 1
336
- end
330
+ http = Net::HTTP.new uri.host, uri.port
331
+ http.use_ssl = true
332
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
333
+
334
+ req = Net::HTTP::Post.new(uri.request_uri, {
335
+ "Connection" => "Keep-Alive",
336
+ "Content-type" => "application/soap+xml; charset=UTF-8",
337
+ "Content-length" => request.bytesize.to_s
338
+ })
339
+ req.body = request
340
+ response = http.request(req)
337
341
 
338
- if c.http_post(request)
339
- response = c.body_str
342
+ if response.code.to_i == 200
343
+ response_body = response.body
340
344
  else
341
345
  # Do something here on error.
342
346
  end
343
- c.close
344
347
 
345
- log_xml("RESPONSE", response)
348
+ log_xml("RESPONSE", response_body)
346
349
 
347
- response
350
+ response_body
348
351
  end
349
352
 
350
353
  def log_xml(title, xml)
@@ -0,0 +1,5 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+ Double = Struct.new(:value)
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  module DynamicsCRM
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -11,6 +11,8 @@ module DynamicsCRM
11
11
  def get_type(key, value)
12
12
  type = "string"
13
13
  case value
14
+ when ::Array
15
+ type = "ArrayOfEntity"
14
16
  when ::Fixnum
15
17
  type = "int"
16
18
  when ::BigDecimal, ::Float
@@ -31,6 +33,8 @@ module DynamicsCRM
31
33
  type = "FetchExpression"
32
34
  when Money
33
35
  type = "Money"
36
+ when DynamicsCRM::Metadata::Double
37
+ type = "double"
34
38
  when DynamicsCRM::Metadata::FilterExpression
35
39
  type = "FilterExpression"
36
40
  when DynamicsCRM::Metadata::PropertiesExpression
@@ -111,6 +115,13 @@ module DynamicsCRM
111
115
  def render_value_xml(type, value)
112
116
  xml = ""
113
117
  case type
118
+ when "ArrayOfEntity"
119
+ raise "We can only serialize Entities inside of ArrayOfEntity" unless value.all?{|a| a.is_a?(DynamicsCRM::XML::Entity)}
120
+ xml << %Q{
121
+ <c:value i:type="a:ArrayOfEntity">
122
+ #{value.map(&->(_) { _.to_xml({in_array: true}) }).join}
123
+ </c:value>
124
+ }
114
125
  when "EntityReference"
115
126
  xml << %Q{
116
127
  <c:value i:type="a:EntityReference">
@@ -131,7 +142,11 @@ module DynamicsCRM
131
142
  s_namespace = "http://schemas.microsoft.com/xrm/2011/Metadata"
132
143
  end
133
144
 
134
- if type == "guid"
145
+ if value.nil?
146
+ xml << %Q{
147
+ <c:value i:nil="true"></c:value>
148
+ }
149
+ elsif type == "guid"
135
150
  xml << %Q{
136
151
  <c:value xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/" i:type="d:guid">#{value}</c:value>
137
152
  }
@@ -143,6 +158,10 @@ module DynamicsCRM
143
158
  xml << %Q{
144
159
  <c:value i:type="s:#{type}" xmlns:s="http://www.w3.org/2001/XMLSchema">#{value.utc.strftime('%Y-%m-%dT%H:%M:%SZ')}</c:value>
145
160
  }
161
+ elsif type == "double"
162
+ xml << %Q{
163
+ <c:value i:type="s:#{type}" xmlns:s="#{s_namespace}">#{value.value}</c:value>
164
+ }
146
165
  else
147
166
  xml << %Q{
148
167
  <c:value i:type="s:#{type}" xmlns:s="#{s_namespace}">#{value}</c:value>
@@ -154,14 +173,14 @@ module DynamicsCRM
154
173
  end
155
174
 
156
175
  def render_object_xml(type, value)
157
- case type
158
- when "EntityQueryExpression"
159
- xml = %Q{<c:value i:type="d:#{type}" xmlns:d="http://schemas.microsoft.com/xrm/2011/Metadata/Query">} << value.to_xml({namespace: 'd'}) << "</c:value>"
160
- else
161
- xml = %Q{<c:value i:type="a:#{type}">} << value.to_xml({exclude_root: true, namespace: 'a'}) << "</c:value>"
162
- end
176
+ case type
177
+ when "EntityQueryExpression"
178
+ xml = %Q{<c:value i:type="d:#{type}" xmlns:d="http://schemas.microsoft.com/xrm/2011/Metadata/Query">} << value.to_xml({namespace: 'd'}) << "</c:value>"
179
+ else
180
+ xml = %Q{<c:value i:type="a:#{type}">} << value.to_xml({exclude_root: true, namespace: 'a'}) << "</c:value>"
181
+ end
163
182
 
164
- return xml
183
+ xml
165
184
  end
166
185
 
167
186
  def class_name
@@ -0,0 +1,48 @@
1
+ module DynamicsCRM
2
+ module XML
3
+ # Loosely based on https://msdn.microsoft.com/en-us/library/gg334419.aspx
4
+ # Creates a ConditionExpression element to be used in retrieve calls.
5
+ class ConditionExpression
6
+ attr_accessor :attr_name, :operator, :value, :type
7
+ def initialize(attr_name, operator, value, type: nil)
8
+ @attr_name = attr_name
9
+ @operator = operator
10
+ @value = value
11
+ @values = Array(value)
12
+ @type = type
13
+ end
14
+
15
+ def value_type
16
+ return type unless type.nil?
17
+
18
+ type = @values.first.class.to_s.downcase
19
+ if type == 'fixnum'
20
+ type = 'int'
21
+ elsif %w(trueclass falseclass).include?(type)
22
+ type = 'boolean'
23
+ end
24
+
25
+ type
26
+ end
27
+
28
+ def to_xml(options = {})
29
+ ns = options[:namespace] ? options[:namespace] : 'a'
30
+
31
+ expression = %(<#{ns}:ConditionExpression>
32
+ <#{ns}:AttributeName>#{attr_name}</#{ns}:AttributeName>
33
+ <#{ns}:Operator>#{operator}</#{ns}:Operator>
34
+ <#{ns}:Values xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
35
+ )
36
+ @values.each do |v|
37
+ expression << %(<d:anyType i:type="s:#{value_type}" xmlns:s="http://www.w3.org/2001/XMLSchema">#{v}</d:anyType>)
38
+ end
39
+
40
+ expression << %(
41
+ </#{ns}:Values>
42
+ </#{ns}:ConditionExpression>)
43
+
44
+ expression
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,52 +1,44 @@
1
1
  module DynamicsCRM
2
2
  module XML
3
-
4
3
  class Criteria < Array
4
+ SUPPORTED_OPERATORS = %w(And Or)
5
+
6
+ def initialize(tuples = [], filter_operator: nil)
7
+ filter_operator ||= 'And'
8
+ raise "Supported operators: #{SUPPORTED_OPERATORS.join(',')}" if !SUPPORTED_OPERATORS.include?(filter_operator)
9
+
10
+ super(tuples)
11
+ @filter_operator = filter_operator
12
+
13
+ # Convert to ConditionExpression
14
+ @expressions = self.map do |tuple|
15
+ attr_name, operator, value, data_type = *tuple
16
+ ConditionExpression.new(attr_name, operator, value, type: data_type)
17
+ end
18
+ end
5
19
 
6
- attr_accessor :filter_operator
7
- def initialize(tuples=[])
8
- super
9
- @filter_operator = 'And'
20
+ def add_condition(attr_name, operator, value, type: nil)
21
+ @expressions << ConditionExpression.new(attr_name, operator, value, type: type)
10
22
  end
11
23
 
12
24
  # ConditionExpression can be repeated multiple times
13
- # Operator: can be lots of values such as: eq (Equals), neq (Not Equals), gt (Greater Than)
25
+ # Operator: can be lots of values such as: eq (Equals), neq (Not Equals), gt (Greater Than)
14
26
  # get the values from a fetch xml query
15
27
  # Values -> Value can be repeated multiple times
16
28
  # FilterOperator: and OR or depending on the filter requirements
17
- def to_xml(options={})
18
- ns = options[:namespace] ? options[:namespace] : "a"
29
+ def to_xml(options = {})
30
+ ns = options[:namespace] ? options[:namespace] : 'a'
19
31
 
20
- expressions = ""
21
- self.each do |tuple|
22
- attr_name = tuple[0]
23
- operator = tuple[1]
24
- values = tuple[2].is_a?(Array) ? tuple[2] : [tuple[2]]
25
- # TODO: Improve type detection
26
- type = (tuple[3] || values.first.class).to_s.downcase
27
- type = "int" if type == "fixnum"
28
- type = "boolean" if ["trueclass", "falseclass"].include?(type)
29
-
30
- expressions << %Q{<#{ns}:ConditionExpression>
31
- <#{ns}:AttributeName>#{attr_name}</#{ns}:AttributeName>
32
- <#{ns}:Operator>#{operator}</#{ns}:Operator>
33
- <#{ns}:Values xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
34
- }
35
- values.each do |v|
36
- expressions << %Q{<d:anyType i:type="s:#{type}" xmlns:s="http://www.w3.org/2001/XMLSchema">#{v}</d:anyType>}
37
- end
38
-
39
- expressions << %Q{
40
- </#{ns}:Values>
41
- </#{ns}:ConditionExpression>}
42
- end
32
+ xml_expression = @expressions.map do |conditional|
33
+ conditional.to_xml(options)
34
+ end.join('')
43
35
 
44
- %Q{<#{ns}:Criteria>
36
+ %(<#{ns}:Criteria>
45
37
  <#{ns}:Conditions>
46
- #{expressions}
38
+ #{xml_expression}
47
39
  </#{ns}:Conditions>
48
40
  <#{ns}:FilterOperator>#{@filter_operator}</#{ns}:FilterOperator>
49
- </#{ns}:Criteria>}
41
+ </#{ns}:Criteria>)
50
42
  end
51
43
  end
52
44
  # Criteria
@@ -24,11 +24,19 @@ module DynamicsCRM
24
24
 
25
25
  return inner_xml if options[:exclude_root]
26
26
 
27
- %Q{
28
- <entity xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
29
- #{inner_xml}
30
- </entity>
31
- }
27
+ if options[:in_array]
28
+ %Q{
29
+ <a:Entity>
30
+ #{inner_xml}
31
+ </a:Entity>
32
+ }
33
+ else
34
+ %Q{
35
+ <entity xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
36
+ #{inner_xml}
37
+ </entity>
38
+ }
39
+ end
32
40
  end
33
41
 
34
42
  def to_hash
@@ -1,26 +1,31 @@
1
1
  module DynamicsCRM
2
2
  module XML
3
-
4
3
  class PageInfo
5
-
6
4
  attr_accessor :count, :page_number, :paging_cookie, :return_total_record_count
7
- def initialize
8
- @count = 20
9
- @page_number = 1
10
- @paging_cookie = nil
11
- @return_total_record_count = false
5
+
6
+ def initialize(count: 20, page_number: 1, paging_cookie: nil, return_total_record_count: false)
7
+ @count = count
8
+ @page_number = page_number
9
+ @paging_cookie = paging_cookie
10
+ @return_total_record_count = return_total_record_count
12
11
  end
13
12
 
14
13
  # Using Entity vs entity causes the error: Value cannot be null.
15
14
  def to_xml
16
- %Q{
15
+ cookie = if paging_cookie.nil?
16
+ '<b:PagingCookie i:nil="true" />'
17
+ else
18
+ %(<b:PagingCookie>#{CGI.escapeHTML(paging_cookie)}</b:PagingCookie>)
19
+ end
20
+
21
+ %(
17
22
  <b:PageInfo>
18
23
  <b:Count>#{count}</b:Count>
19
24
  <b:PageNumber>#{page_number}</b:PageNumber>
20
- <b:PagingCookie i:nil="true" />
21
- <b:ReturnTotalRecordCount>false</b:ReturnTotalRecordCount>
25
+ #{cookie}
26
+ <b:ReturnTotalRecordCount>#{return_total_record_count}</b:ReturnTotalRecordCount>
22
27
  </b:PageInfo>
23
- }
28
+ )
24
29
  end
25
30
 
26
31
  def to_hash
@@ -31,8 +36,7 @@ module DynamicsCRM
31
36
  :return_total_record_count => return_total_record_count
32
37
  }
33
38
  end
34
-
35
39
  end
36
40
  # PageInfo
37
41
  end
38
- end
42
+ end
@@ -0,0 +1,43 @@
1
+ module DynamicsCRM
2
+ module XML
3
+ # Represents QueryExpression XML fragment.
4
+ class QueryExpression
5
+ attr_accessor :columns, :criteria, :entity_name, :page_info
6
+
7
+ def initialize(entity_name)
8
+ @entity_name = entity_name
9
+ @criteria = Criteria.new
10
+ end
11
+
12
+ def to_xml(options = {})
13
+ namespace = options[:namespace] ? options[:namespace] : 'b'
14
+
15
+ column_set = columns.is_a?(ColumnSet) ? columns : ColumnSet.new(columns)
16
+
17
+ xml = %(
18
+ #{column_set.to_xml(namespace: namespace, camel_case: true)}
19
+ #{criteria.to_xml(namespace: namespace)}
20
+ <#{namespace}:Distinct>false</#{namespace}:Distinct>
21
+ <#{namespace}:EntityName>#{entity_name}</#{namespace}:EntityName>
22
+ <#{namespace}:LinkEntities />
23
+ <#{namespace}:Orders />
24
+ )
25
+
26
+ xml << page_info.to_xml if page_info
27
+
28
+ if options[:exclude_root].nil?
29
+ xml = %(<query i:type="b:QueryExpression" xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
30
+ #{xml}
31
+ </query>)
32
+ end
33
+
34
+ xml
35
+ end
36
+ end
37
+ # QueryExpression
38
+
39
+ # Backward compatible class
40
+ class Query < QueryExpression
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2
+ <s:Body>
3
+ <Create xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
4
+ <entity xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
5
+ <a:Attributes xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
6
+ <a:KeyValuePairOfstringanyType>
7
+ <b:key>from</b:key>
8
+ <b:value i:type="a:ArrayOfEntity">
9
+ <a:Entity>
10
+ <a:Attributes>
11
+ <a:KeyValuePairOfstringanyType>
12
+ <b:key>partyid</b:key>
13
+ <b:value i:type="a:EntityReference">
14
+ <a:Id>f36aa96c-e7a5-4c70-8254-47c8ba947561</a:Id>
15
+ <a:LogicalName>systemuser</a:LogicalName>
16
+ <a:Name i:nil="true" />
17
+ </b:value>
18
+ </a:KeyValuePairOfstringanyType>
19
+ </a:Attributes>
20
+ <a:EntityState i:nil="true" />
21
+ <a:FormattedValues />
22
+ <a:Id>00000000-0000-0000-0000-000000000000</a:Id>
23
+ <a:LogicalName>activityparty</a:LogicalName>
24
+ <a:RelatedEntities />
25
+ </a:Entity>
26
+ </b:value>
27
+ </a:KeyValuePairOfstringanyType>
28
+ </a:Attributes>
29
+ <a:EntityState i:nil="true" />
30
+ <a:FormattedValues xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
31
+ <a:Id>00000000-0000-0000-0000-000000000000</a:Id>
32
+ <a:LogicalName>phonecall</a:LogicalName>
33
+ <a:RelatedEntities xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
34
+ </entity>
35
+ </Create>
36
+ </s:Body>
37
+ </s:Envelope>
@@ -36,7 +36,7 @@ describe DynamicsCRM::Client do
36
36
  end
37
37
 
38
38
  context "On-Premise" do
39
- let(:subject) { DynamicsCRM::Client.new(organization_name: "psavtest", hostname: "psavtest.crm.powerobjects.net")}
39
+ let(:subject) { DynamicsCRM::Client.new(hostname: "customers.crm.psav.com")}
40
40
 
41
41
  it "authenticates with username and password" do
42
42
 
@@ -110,6 +110,30 @@ describe DynamicsCRM::Client do
110
110
  expect(entities[1].attributes["accountid"]).to eq("dbe9d7c9-2c98-e311-9752-6c3be5a87df0")
111
111
  expect(entities[2].attributes["accountid"]).to eq("8ff0325c-a592-e311-b7f3-6c3be5a8a0c8")
112
112
  end
113
+
114
+ it "retrieves multiple entities by criteria using OR" do
115
+
116
+ allow(subject).to receive(:post).and_return(fixture("retrieve_multiple_result"))
117
+
118
+ result = subject.retrieve_multiple("account", ["name", "Equal", "Test Account"], columns=[], 'Or')
119
+
120
+ expect(result).to be_a(DynamicsCRM::Response::RetrieveMultipleResult)
121
+ end
122
+
123
+ it "retrieves multiple entities by QueryExpression" do
124
+ allow(subject).to receive(:post).and_return(fixture("retrieve_multiple_result"))
125
+
126
+ query = DynamicsCRM::XML::QueryExpression.new('account')
127
+ query.columns = %w(accountid name)
128
+ query.criteria.add_condition('name', 'Equal', 'Test Account')
129
+
130
+ result = subject.retrieve_multiple(query)
131
+
132
+ expect(result).to be_a(DynamicsCRM::Response::RetrieveMultipleResult)
133
+
134
+ expect(result['EntityName']).to eq('account')
135
+ expect(result.entities.size).to eq(3)
136
+ end
113
137
  end
114
138
 
115
139
  describe "#retrieve_attachments" do
@@ -9,7 +9,9 @@ describe DynamicsCRM::XML::Attributes do
9
9
  "modifiedon" => Time.now,
10
10
  "donotemail" => true,
11
11
  "id" => "1bfa3886-df7e-468c-8435-b5adfb0441ed",
12
- "reference" => {"Id" => "someid", "Name" => "entityname", "LogicalName" => "opportunity"}
12
+ "reference" => {"Id" => "someid", "Name" => "entityname", "LogicalName" => "opportunity"},
13
+ "expireson" => nil,
14
+ "address1_latitude" => DynamicsCRM::Metadata::Double.new(5.22123)
13
15
  }
14
16
  }
15
17
  subject {
@@ -32,6 +34,11 @@ describe DynamicsCRM::XML::Attributes do
32
34
  it { expect(subject.to_xml).to include("<c:key>telephone1</c:key>") }
33
35
  it { expect(subject.to_xml).to include("<c:key>donotemail</c:key>") }
34
36
  it { expect(subject.to_xml).to include("<c:key>modifiedon</c:key>") }
37
+ it { expect(subject.to_xml).to include('<c:value i:nil="true"></c:value>') }
38
+ it do
39
+ expect(subject.to_xml)
40
+ .to include('<c:value i:type="s:double" xmlns:s="http://www.w3.org/2001/XMLSchema">5.22123</c:value>')
41
+ end
35
42
  end
36
43
 
37
44
  end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe DynamicsCRM::XML::Criteria do
4
+ describe 'initialization' do
5
+ subject do
6
+ DynamicsCRM::XML::Criteria.new
7
+ end
8
+
9
+ it 'generates empty Criteria fragment' do
10
+ expected = %(<a:Criteria>
11
+ <a:Conditions>
12
+ </a:Conditions>
13
+ <a:FilterOperator>And</a:FilterOperator>
14
+ </a:Criteria>)
15
+
16
+ expect(subject.to_xml).to match_xml(expected)
17
+ end
18
+ end
19
+
20
+ describe 'single criteria' do
21
+ subject do
22
+ DynamicsCRM::XML::Criteria.new([['name', 'Equal', 'Test Opp']])
23
+ end
24
+ let(:expected) {
25
+ %(<a:Criteria>
26
+ <a:Conditions>
27
+ <a:ConditionExpression>
28
+ <a:AttributeName>name</a:AttributeName>
29
+ <a:Operator>Equal</a:Operator>
30
+ <a:Values xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
31
+ <d:anyType i:type="s:string" xmlns:s="http://www.w3.org/2001/XMLSchema">Test Opp</d:anyType>
32
+ </a:Values>
33
+ </a:ConditionExpression>
34
+ </a:Conditions>
35
+ <a:FilterOperator>And</a:FilterOperator>
36
+ </a:Criteria>)
37
+ }
38
+
39
+ it 'generates Criteria fragment with single ConditionExpression' do
40
+ expect(subject.to_xml).to match_xml expected
41
+ end
42
+
43
+ it 'set data type explicitly' do
44
+ # Supports optional fourth value for data type
45
+ subject = DynamicsCRM::XML::Criteria.new([['name', 'Equal', 'Test Opp', 'customstring']])
46
+
47
+ expect(subject.to_xml).to match_xml expected.gsub('s:string', 's:customstring')
48
+ end
49
+ end
50
+
51
+ describe 'multiple criteria' do
52
+ subject do
53
+ DynamicsCRM::XML::Criteria.new([
54
+ ['name', 'Equal', 'Test Opp'],
55
+ ['salesstage', 'In', [0, 1, 2]],
56
+ ])
57
+ end
58
+
59
+ it 'generates Criteria with multiple ConditionExpression(s)' do
60
+ expect(subject.to_xml).to match_xml %(<a:Criteria>
61
+ <a:Conditions>
62
+ <a:ConditionExpression>
63
+ <a:AttributeName>name</a:AttributeName>
64
+ <a:Operator>Equal</a:Operator>
65
+ <a:Values xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
66
+ <d:anyType i:type="s:string" xmlns:s="http://www.w3.org/2001/XMLSchema">Test Opp</d:anyType>
67
+ </a:Values>
68
+ </a:ConditionExpression>
69
+ <a:ConditionExpression>
70
+ <a:AttributeName>salesstage</a:AttributeName>
71
+ <a:Operator>In</a:Operator>
72
+ <a:Values xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
73
+ <d:anyType i:type="s:int" xmlns:s="http://www.w3.org/2001/XMLSchema">0</d:anyType>
74
+ <d:anyType i:type="s:int" xmlns:s="http://www.w3.org/2001/XMLSchema">1</d:anyType>
75
+ <d:anyType i:type="s:int" xmlns:s="http://www.w3.org/2001/XMLSchema">2</d:anyType>
76
+ </a:Values>
77
+ </a:ConditionExpression>
78
+ </a:Conditions>
79
+ <a:FilterOperator>And</a:FilterOperator>
80
+ </a:Criteria>)
81
+ end
82
+ end
83
+ end
@@ -60,4 +60,18 @@ describe DynamicsCRM::XML::Entity do
60
60
  end
61
61
  end
62
62
 
63
+ describe "entity with array" do
64
+ subject {
65
+ entity = DynamicsCRM::XML::Entity.new("activityparty")
66
+ entity.attributes = DynamicsCRM::XML::Attributes.new(
67
+ partyid: DynamicsCRM::XML::EntityReference.new("systemuser", "f36aa96c-e7a5-4c70-8254-47c8ba947561")
68
+ )
69
+ entity
70
+ }
71
+
72
+ context "#to_xml" do
73
+ it { expect(DynamicsCRM::XML::Attributes.new({to: [subject]}).to_xml).to include('<c:value i:type="a:ArrayOfEntity">') }
74
+ end
75
+ end
76
+
63
77
  end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe DynamicsCRM::XML::QueryExpression do
4
+
5
+ describe 'initialization' do
6
+ subject {
7
+ DynamicsCRM::XML::QueryExpression.new('opportunity')
8
+ }
9
+
10
+ context "generate empty QueryExpression fragment" do
11
+ it { expect(subject.to_xml).to include("<b:ColumnSet ") }
12
+ it { expect(subject.to_xml).to match(/<b:Conditions>\s+<\/b:Conditions>/) }
13
+ it { expect(subject.to_xml).to include("<b:AllColumns>true</b:AllColumns>") }
14
+ it { expect(subject.to_xml).to include("<b:Distinct>false</b:Distinct>") }
15
+ it { expect(subject.to_xml).to include("<b:EntityName>opportunity</b:EntityName>") }
16
+ it { expect(subject.to_xml).to include("<b:FilterOperator>And</b:FilterOperator>").or(include("<b:FilterOperator>Or</b:FilterOperator>")) }
17
+ end
18
+ end
19
+
20
+ describe 'Criteria' do
21
+ subject {
22
+ query = DynamicsCRM::XML::QueryExpression.new('opportunity')
23
+ query.criteria = DynamicsCRM::XML::Criteria.new([["name", "Equal", "Test Opp"]])
24
+ query
25
+ }
26
+
27
+ context "generate QueryExpression fragment" do
28
+ it { expect(subject.to_xml).to include("<b:ColumnSet ") }
29
+ it { expect(subject.to_xml).to include("<b:ConditionExpression") }
30
+ it { expect(subject.to_xml).to include("AttributeName>name</") }
31
+ it { expect(subject.to_xml).to include("Operator>Equal</") }
32
+ it { expect(subject.to_xml).to include('<d:anyType i:type="s:string" xmlns:s="http://www.w3.org/2001/XMLSchema">Test Opp</d:anyType>') }
33
+ it { expect(subject.to_xml).to include("<b:AllColumns>true</b:AllColumns>") }
34
+ it { expect(subject.to_xml).to include("<b:Distinct>false</b:Distinct>") }
35
+ it { expect(subject.to_xml).to include("<b:EntityName>opportunity</b:EntityName>") }
36
+ it { expect(subject.to_xml).to include("<b:FilterOperator>And</b:FilterOperator>").or(include("<b:FilterOperator>Or</b:FilterOperator>")) }
37
+ end
38
+ end
39
+
40
+ describe 'PageInfo' do
41
+ subject {
42
+ query = DynamicsCRM::XML::QueryExpression.new('account')
43
+ query.columns = %w(accountid name)
44
+ query.criteria.add_condition('name', 'NotEqual', 'Test Account')
45
+ query.page_info = DynamicsCRM::XML::PageInfo.new(count: 5, page_number: 2, return_total_record_count: true)
46
+ query
47
+ }
48
+
49
+ context "generate empty QueryExpression fragment" do
50
+ it { expect(subject.to_xml).to include('<b:ColumnSet ') }
51
+ it { expect(subject.to_xml).to include('<b:ConditionExpression') }
52
+ it { expect(subject.to_xml).to include('AttributeName>name</') }
53
+ it { expect(subject.to_xml).to include('Operator>NotEqual</') }
54
+ it { expect(subject.to_xml).to include('<d:anyType i:type="s:string" xmlns:s="http://www.w3.org/2001/XMLSchema">Test Account</d:anyType>') }
55
+ it { expect(subject.to_xml).to include('<b:AllColumns>false</b:AllColumns>') }
56
+ it { expect(subject.to_xml).to include('<b:Columns') }
57
+ it { expect(subject.to_xml).to include('<b:EntityName>account</b:EntityName>') }
58
+ it { expect(subject.to_xml).to include('<b:PageInfo>') }
59
+ it { expect(subject.to_xml).to include('<b:Count>5</b:Count>') }
60
+ it { expect(subject.to_xml).to include('<b:PageNumber>2</b:PageNumber>') }
61
+ it { expect(subject.to_xml).to include('<b:ReturnTotalRecordCount>true</b:ReturnTotalRecordCount>') }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ RSpec::Matchers.define :match_xml do |expected|
2
+ match do |actual|
3
+ clean_xml(expected) == clean_xml(actual)
4
+ end
5
+
6
+ def clean_xml(str)
7
+ str.split("\n").map(&:strip).join('')
8
+ end
9
+ end
metadata CHANGED
@@ -1,35 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamics_crm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Heth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-08 00:00:00.000000000 Z
11
+ date: 2017-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: curb
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0.8'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: 1.0.0
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: '0.8'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: 1.0.0
33
13
  - !ruby/object:Gem::Dependency
34
14
  name: mimemagic
35
15
  requirement: !ruby/object:Gem::Requirement
@@ -158,7 +138,7 @@ dependencies:
158
138
  - - "~>"
159
139
  - !ruby/object:Gem::Version
160
140
  version: 0.10.3
161
- description: Ruby API for integrating with MS Dynamics 2011/2013 SOAP API
141
+ description: Ruby API for integrating with MS Dynamics SOAP API
162
142
  email:
163
143
  - joeheth@gmail.com
164
144
  executables: []
@@ -181,6 +161,7 @@ files:
181
161
  - lib/dynamics_crm/fetch_xml/link_entity.rb
182
162
  - lib/dynamics_crm/metadata/attribute_metadata.rb
183
163
  - lib/dynamics_crm/metadata/attribute_query_expression.rb
164
+ - lib/dynamics_crm/metadata/double.rb
184
165
  - lib/dynamics_crm/metadata/entity_metadata.rb
185
166
  - lib/dynamics_crm/metadata/entity_query_expression.rb
186
167
  - lib/dynamics_crm/metadata/filter_expression.rb
@@ -202,6 +183,7 @@ files:
202
183
  - lib/dynamics_crm/version.rb
203
184
  - lib/dynamics_crm/xml/attributes.rb
204
185
  - lib/dynamics_crm/xml/column_set.rb
186
+ - lib/dynamics_crm/xml/condition_expression.rb
205
187
  - lib/dynamics_crm/xml/criteria.rb
206
188
  - lib/dynamics_crm/xml/entity.rb
207
189
  - lib/dynamics_crm/xml/entity_collection.rb
@@ -213,11 +195,12 @@ files:
213
195
  - lib/dynamics_crm/xml/money.rb
214
196
  - lib/dynamics_crm/xml/orders.rb
215
197
  - lib/dynamics_crm/xml/page_info.rb
216
- - lib/dynamics_crm/xml/query.rb
198
+ - lib/dynamics_crm/xml/query_expression.rb
217
199
  - spec/fixtures/associate_response.xml
218
200
  - spec/fixtures/create_response.xml
219
201
  - spec/fixtures/delete_response.xml
220
202
  - spec/fixtures/disassociate_response.xml
203
+ - spec/fixtures/entity_array_response.xml
221
204
  - spec/fixtures/fetch_xml_response.xml
222
205
  - spec/fixtures/lose_opportunity_response.xml
223
206
  - spec/fixtures/receiver_fault.xml
@@ -248,18 +231,20 @@ files:
248
231
  - spec/lib/metadata/retrieve_metadata_changes_response_spec.rb
249
232
  - spec/lib/model/opportunity_spec.rb
250
233
  - spec/lib/response/execute_result_spec.rb
251
- - spec/lib/response/retrieve_multiple_spec.rb
234
+ - spec/lib/response/retrieve_multiple_result_spec.rb
252
235
  - spec/lib/response/retrieve_result_spec.rb
253
236
  - spec/lib/xml/attributes_spec.rb
254
237
  - spec/lib/xml/column_set_spec.rb
238
+ - spec/lib/xml/criteria_spec.rb
255
239
  - spec/lib/xml/entity_reference_spec.rb
256
240
  - spec/lib/xml/entity_spec.rb
257
241
  - spec/lib/xml/fault_spec.rb
258
242
  - spec/lib/xml/message_builder_spec.rb
259
243
  - spec/lib/xml/money_spec.rb
260
- - spec/lib/xml/query_spec.rb
244
+ - spec/lib/xml/query_expression_spec.rb
261
245
  - spec/spec_helper.rb
262
246
  - spec/support/fixture_helpers.rb
247
+ - spec/support/matchers/match_xml.rb
263
248
  homepage: https://github.com/TinderBox/dynamics_crm
264
249
  licenses:
265
250
  - MIT
@@ -280,15 +265,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
280
265
  version: '0'
281
266
  requirements: []
282
267
  rubyforge_project:
283
- rubygems_version: 2.4.8
268
+ rubygems_version: 2.5.1
284
269
  signing_key:
285
270
  specification_version: 4
286
- summary: Ruby gem for integrating with MS Dynamics 2011/2013 SOAP API
271
+ summary: Ruby gem for integrating with MS Dynamics SOAP API
287
272
  test_files:
288
273
  - spec/fixtures/associate_response.xml
289
274
  - spec/fixtures/create_response.xml
290
275
  - spec/fixtures/delete_response.xml
291
276
  - spec/fixtures/disassociate_response.xml
277
+ - spec/fixtures/entity_array_response.xml
292
278
  - spec/fixtures/fetch_xml_response.xml
293
279
  - spec/fixtures/lose_opportunity_response.xml
294
280
  - spec/fixtures/receiver_fault.xml
@@ -319,15 +305,17 @@ test_files:
319
305
  - spec/lib/metadata/retrieve_metadata_changes_response_spec.rb
320
306
  - spec/lib/model/opportunity_spec.rb
321
307
  - spec/lib/response/execute_result_spec.rb
322
- - spec/lib/response/retrieve_multiple_spec.rb
308
+ - spec/lib/response/retrieve_multiple_result_spec.rb
323
309
  - spec/lib/response/retrieve_result_spec.rb
324
310
  - spec/lib/xml/attributes_spec.rb
325
311
  - spec/lib/xml/column_set_spec.rb
312
+ - spec/lib/xml/criteria_spec.rb
326
313
  - spec/lib/xml/entity_reference_spec.rb
327
314
  - spec/lib/xml/entity_spec.rb
328
315
  - spec/lib/xml/fault_spec.rb
329
316
  - spec/lib/xml/message_builder_spec.rb
330
317
  - spec/lib/xml/money_spec.rb
331
- - spec/lib/xml/query_spec.rb
318
+ - spec/lib/xml/query_expression_spec.rb
332
319
  - spec/spec_helper.rb
333
320
  - spec/support/fixture_helpers.rb
321
+ - spec/support/matchers/match_xml.rb
@@ -1,38 +0,0 @@
1
- module DynamicsCRM
2
- module XML
3
- # Represents Query XML fragment.
4
- class Query
5
-
6
- attr_accessor :columns, :criteria, :entity_name
7
-
8
- def initialize(entity_name)
9
- @entity_name = entity_name
10
- end
11
-
12
- def to_xml(options={})
13
- namespace = options[:namespace] ? options[:namespace] : "b"
14
-
15
- column_set = ColumnSet.new(columns)
16
- @criteria ||= Criteria.new
17
-
18
- xml = %Q{
19
- #{column_set.to_xml(namespace: namespace, camel_case: true)}
20
- #{@criteria.to_xml(namespace: namespace)}
21
- <#{namespace}:Distinct>false</#{namespace}:Distinct>
22
- <#{namespace}:EntityName>#{entity_name}</#{namespace}:EntityName>
23
- <#{namespace}:LinkEntities />
24
- <#{namespace}:Orders />
25
- }
26
-
27
- if options[:exclude_root].nil?
28
- xml = %Q{<query i:type="b:QueryExpression" xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
29
- #{xml}
30
- </query>}
31
- end
32
- return xml
33
- end
34
-
35
- end
36
- # Query
37
- end
38
- end
@@ -1,43 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe DynamicsCRM::XML::Query do
4
-
5
- describe 'initialization' do
6
- subject {
7
- DynamicsCRM::XML::Query.new('opportunity')
8
- }
9
-
10
- context "generate empty Query fragment" do
11
- it { expect(subject.to_xml).to include("<b:ColumnSet ") }
12
- it { expect(subject.to_xml).to match(/<b:Conditions>\s+<\/b:Conditions>/) }
13
- it { expect(subject.to_xml).to include("<b:AllColumns>true</b:AllColumns>") }
14
- it { expect(subject.to_xml).to include("<b:Distinct>false</b:Distinct>") }
15
- it { expect(subject.to_xml).to include("<b:EntityName>opportunity</b:EntityName>") }
16
- it { expect(subject.to_xml).to include("<b:FilterOperator>And</b:FilterOperator>") }
17
- end
18
-
19
- end
20
-
21
-
22
- describe 'criteria' do
23
- subject {
24
- query = DynamicsCRM::XML::Query.new('opportunity')
25
- query.criteria = DynamicsCRM::XML::Criteria.new([["name", "Equal", "Test Opp"]])
26
- query
27
- }
28
-
29
- context "generate empty Query fragment" do
30
- it { expect(subject.to_xml).to include("<b:ColumnSet ") }
31
- it { expect(subject.to_xml).to include("<b:ConditionExpression") }
32
- it { expect(subject.to_xml).to include("AttributeName>name</") }
33
- it { expect(subject.to_xml).to include("Operator>Equal</") }
34
- it { expect(subject.to_xml).to include('<d:anyType i:type="s:string" xmlns:s="http://www.w3.org/2001/XMLSchema">Test Opp</d:anyType>') }
35
- it { expect(subject.to_xml).to include("<b:AllColumns>true</b:AllColumns>") }
36
- it { expect(subject.to_xml).to include("<b:Distinct>false</b:Distinct>") }
37
- it { expect(subject.to_xml).to include("<b:EntityName>opportunity</b:EntityName>") }
38
- it { expect(subject.to_xml).to include("<b:FilterOperator>And</b:FilterOperator>") }
39
- end
40
-
41
- end
42
-
43
- end