diff --git a/Gemfile b/Gemfile index a07d02ec..0b167af7 100644 --- a/Gemfile +++ b/Gemfile @@ -3,10 +3,11 @@ source 'https://rubygems.org/' gemspec group :development do - gem 'rspec', '<3.0' - gem 'guard' + gem 'awesome_print' gem 'guard-rspec' - gem 'rb-inotify', :require => false + gem 'guard' + gem 'pry-nav' + gem 'rb-inotify', require: false + gem 'rspec', '<3.0' gem 'turn' - gem "pry-nav" end diff --git a/README.md b/README.md index 5419d16c..373c479d 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,17 @@ inbox.items_since sd sd = Date.iso8601 '2013-01-01' ed = Date.iso8601 '2013-02-01' inbox.items_between sd, ed + +# with paging +inbox.paging_items 10 + +# paging with parameters where: +# first parameter is the maximum number of items returned +# second parameter is the offset from the starting point +# and the last parameter is the starting point in the list of items +# the valid options for this parameter are 'Beginning' and 'End' +# the default is 'Beginning' +inbox.paging_items 10, 5, 'End' ``` ### Free/Busy Calendar Accessors diff --git a/lib/ews/soap/parsers/ews_dom_parser.rb b/lib/ews/soap/parsers/ews_dom_parser.rb new file mode 100644 index 00000000..914e12fd --- /dev/null +++ b/lib/ews/soap/parsers/ews_dom_parser.rb @@ -0,0 +1,28 @@ +module Viewpoint::EWS::SOAP + class EwsDomParser + include Viewpoint::EWS + include Viewpoint::StringUtils + + def self.parse(soap_resp) + doc = Nokogiri::XML soap_resp + node_hash doc.root + end + + def self.node_hash(node) + node_hash_value = {} + node_hash_value[:elems] = node.element_children.map { |child_element| node_hash child_element } unless node.element_children.empty? + node_hash_value[:attribs] = attributes_hash(node) unless node.attributes.empty? + node_hash_value[:text] = node.children.first.content if !node.children.empty? && node.children.first.text? + { ruby_case(node.name).to_sym => node_hash_value } + end + + def self.attributes_hash(node) + attr_hash = {} + node.attributes.each do |attr| + attr_hash[ruby_case(attr.first).to_sym] = attr[1].value + end + + attr_hash + end + end +end diff --git a/lib/ews/soap/parsers/ews_parser.rb b/lib/ews/soap/parsers/ews_parser.rb index 0e8fc8a9..6fcf823e 100644 --- a/lib/ews/soap/parsers/ews_parser.rb +++ b/lib/ews/soap/parsers/ews_parser.rb @@ -23,21 +23,11 @@ class EwsParser # @param [String] soap_resp def initialize(soap_resp) @soap_resp = soap_resp - @sax_doc = EwsSaxDocument.new end def parse(opts = {}) opts[:response_class] ||= EwsSoapResponse - sax_parser.parse(@soap_resp) - opts[:response_class].new @sax_doc.struct + opts[:response_class].new Viewpoint::EWS::SOAP::EwsDomParser.parse(@soap_resp) end - - - private - - def sax_parser - @parser ||= Nokogiri::XML::SAX::Parser.new(@sax_doc) - end - end # EwsParser end # Viewpoint diff --git a/lib/ews/soap/parsers/ews_sax_document.rb b/lib/ews/soap/parsers/ews_sax_document.rb index e2a3a427..7a63ea16 100644 --- a/lib/ews/soap/parsers/ews_sax_document.rb +++ b/lib/ews/soap/parsers/ews_sax_document.rb @@ -48,14 +48,14 @@ def start_element_namespace(name, attributes = [], prefix = nil, uri = nil, ns = name = ruby_case(name).to_sym elem = {} unless attributes.empty? - elem[:attribs] = attributes.collect{|a| - { ruby_case(a.localname).to_sym => a.value} - }.inject(&:merge) + elem[:attribs] = attributes.collect do |a| + { ruby_case(a.localname).to_sym => a.value } + end.inject(&:merge) end @elems << elem end - def end_element_namespace name, prefix=nil, uri=nil + def end_element_namespace(name, prefix = nil, uri = nil) name = ruby_case(name).to_sym elem = @elems.pop if @elems.empty? @@ -65,6 +65,5 @@ def end_element_namespace name, prefix=nil, uri=nil @elems.last[:elems] << {name => elem} end end - end end diff --git a/lib/ews/types/generic_folder.rb b/lib/ews/types/generic_folder.rb index b3a85aa9..a5a9be6d 100644 --- a/lib/ews/types/generic_folder.rb +++ b/lib/ews/types/generic_folder.rb @@ -124,6 +124,19 @@ def items_between(start_date, end_date, opts={}) end end + # Fetch items with paging + # @param [Integer] max_entries Maximum number of items returned + # @param [Integer] offset Offset from the starting point. This is optional. + # @param [String] basepoint Starting point in the list of items. + # The valid options for this parameter are 'Beginning' and 'End'. This is optional. + def paging_items(max_entries, offset = 0, basepoint = 'Beginning') + items(indexed_page_item_view: { + 'MaxEntriesReturned' => max_entries, + 'Offset' => offset, + 'BasePoint' => basepoint + }) + end + # Search on the item subject # @param [String] match_str A simple string paramater to match against the # subject. The search ignores case and does not accept regexes... only strings. diff --git a/lib/ews/types/item_field_uri_map.rb b/lib/ews/types/item_field_uri_map.rb index 30a13368..b9c8d986 100644 --- a/lib/ews/types/item_field_uri_map.rb +++ b/lib/ews/types/item_field_uri_map.rb @@ -34,7 +34,6 @@ module ItemFieldUriMap :effective_rights => {:text => 'folder:EffectiveRights', :writable => true}, :sharing_effective_rights => {:text => 'folder:SharingEffectiveRights', :writable => true}, :item_id => {:text => 'item:ItemId', :writable => true}, - :parent_folder_id => {:text => 'item:ParentFolderId', :writable => true}, :item_class => {:text => 'item:ItemClass', :writable => true}, :mime_content => {:text => 'item:MimeContent', :writable => true}, :attachments => {:text => 'item:Attachments', :writable => true}, @@ -63,7 +62,6 @@ module ItemFieldUriMap :display_to => {:text => 'item:DisplayTo', :writable => true}, :display_cc => {:text => 'item:DisplayCc', :writable => true}, :culture => {:text => 'item:Culture', :writable => true}, - :effective_rights => {:text => 'item:EffectiveRights', :writable => true}, :last_modified_name => {:text => 'item:LastModifiedName', :writable => true}, :last_modified_time => {:text => 'item:LastModifiedTime', :writable => true}, :conversation_id => {:text => 'item:ConversationId', :writable => true}, @@ -102,7 +100,6 @@ module ItemFieldUriMap :is_cancelled => {:text => 'calendar:IsCancelled', :writable => true}, :is_recurring => {:text => 'calendar:IsRecurring', :writable => true}, :meeting_request_was_sent => {:text => 'calendar:MeetingRequestWasSent', :writable => true}, - :is_response_requested => {:text => 'calendar:IsResponseRequested', :writable => true}, :calendar_item_type => {:text => 'calendar:CalendarItemType', :writable => true}, :my_response_type => {:text => 'calendar:MyResponseType', :writable => true}, :organizer => {:text => 'calendar:Organizer', :writable => true}, @@ -146,12 +143,10 @@ module ItemFieldUriMap :due_date => {:text => 'task:DueDate', :writable => true}, :is_assignment_editable => {:text => 'task:IsAssignmentEditable', :writable => true}, :is_complete => {:text => 'task:IsComplete', :writable => true}, - :is_recurring => {:text => 'task:IsRecurring', :writable => true}, :is_team_task => {:text => 'task:IsTeamTask', :writable => true}, :mileage => {:text => 'task:Mileage', :writable => true}, :owner => {:text => 'task:Owner', :writable => true}, :percent_complete => {:text => 'task:PercentComplete', :writable => true}, - :recurrence => {:text => 'task:Recurrence', :writable => true}, :start_date => {:text => 'task:StartDate', :writable => true}, :status => {:text => 'task:Status', :writable => true}, :status_description => {:text => 'task:StatusDescription', :writable => true}, @@ -160,13 +155,10 @@ module ItemFieldUriMap :birthday => {:text => 'contacts:Birthday', :writable => true}, :business_home_page => {:text => 'contacts:BusinessHomePage', :writable => true}, :children => {:text => 'contacts:Children', :writable => true}, - :companies => {:text => 'contacts:Companies', :writable => true}, :company_name => {:text => 'contacts:CompanyName', :writable => true}, :complete_name => {:text => 'contacts:CompleteName', :writable => true}, :contact_source => {:text => 'contacts:ContactSource', :writable => true}, - :culture => {:text => 'contacts:Culture', :writable => true}, :department => {:text => 'contacts:Department', :writable => true}, - :display_name => {:text => 'contacts:DisplayName', :writable => true}, :email_addresses => {:ftype => :indexed_field_uRI, :text => 'contacts:EmailAddress', :writable => true}, :file_as => {:text => 'contacts:FileAs', :writable => true}, :file_as_mapping => {:text => 'contacts:FileAsMapping', :writable => true}, @@ -178,7 +170,6 @@ module ItemFieldUriMap :job_title => {:text => 'contacts:JobTitle', :writable => true}, :manager => {:text => 'contacts:Manager', :writable => true}, :middle_name => {:text => 'contacts:MiddleName', :writable => true}, - :mileage => {:text => 'contacts:Mileage', :writable => true}, :nickname => {:text => 'contacts:Nickname', :writable => true}, :office_location => {:text => 'contacts:OfficeLocation', :writable => true}, :phone_numbers => {:ftype => :indexed_field_uRI, :text => 'contacts:PhoneNumber', :writable => true}, @@ -190,8 +181,6 @@ module ItemFieldUriMap :wedding_anniversary => {:text => 'contacts:WeddingAnniversary', :writable => true}, :members => {:text => 'distributionlist:Members', :writable => true}, :posted_time => {:text => 'postitem:PostedTime', :writable => true}, - :conversation_id => {:text => 'conversation:ConversationId', :writable => true}, - :conversation_topic => {:text => 'conversation:ConversationTopic', :writable => true}, :unique_recipients => {:text => 'conversation:UniqueRecipients', :writable => true}, :global_unique_recipients => {:text => 'conversation:GlobalUniqueRecipients', :writable => true}, :unique_unread_senders => {:text => 'conversation:UniqueUnreadSenders', :writable => true}, @@ -200,21 +189,16 @@ module ItemFieldUriMap :global_unique_senders => {:text => 'conversation:GlobalUniqueSenders', :writable => true}, :last_delivery_time => {:text => 'conversation:LastDeliveryTime', :writable => true}, :global_last_delivery_time => {:text => 'conversation:GlobalLastDeliveryTime', :writable => true}, - :categories => {:text => 'conversation:Categories', :writable => true}, :global_categories => {:text => 'conversation:GlobalCategories', :writable => true}, :flag_status => {:text => 'conversation:FlagStatus', :writable => true}, :global_flag_status => {:text => 'conversation:GlobalFlagStatus', :writable => true}, - :has_attachments => {:text => 'conversation:HasAttachments', :writable => true}, :global_has_attachments => {:text => 'conversation:GlobalHasAttachments', :writable => true}, :message_count => {:text => 'conversation:MessageCount', :writable => true}, :global_message_count => {:text => 'conversation:GlobalMessageCount', :writable => true}, - :unread_count => {:text => 'conversation:UnreadCount', :writable => true}, :global_unread_count => {:text => 'conversation:GlobalUnreadCount', :writable => true}, - :size => {:text => 'conversation:Size', :writable => true}, :global_size => {:text => 'conversation:GlobalSize', :writable => true}, :item_classes => {:text => 'conversation:ItemClasses', :writable => true}, :global_item_classes => {:text => 'conversation:GlobalItemClasses', :writable => true}, - :importance => {:text => 'conversation:Importance', :writable => true}, :global_importance => {:text => 'conversation:GlobalImportance', :writable => true}, :item_ids => {:text => 'conversation:ItemIds', :writable => true}, :global_item_ids => {:text => 'conversation:GlobalItemIds', :writable => true} diff --git a/lib/viewpoint.rb b/lib/viewpoint.rb index a8f66734..f04a7bb2 100644 --- a/lib/viewpoint.rb +++ b/lib/viewpoint.rb @@ -44,6 +44,7 @@ require 'ews/soap/builders/ews_builder' require 'ews/soap/parsers/ews_parser' require 'ews/soap/parsers/ews_sax_document' +require 'ews/soap/parsers/ews_dom_parser' # Mix-ins for the ExchangeWebService require 'ews/soap/exchange_data_services' require 'ews/soap/exchange_notification' diff --git a/spec/soap_data/get_text_item_response.xml b/spec/soap_data/get_text_item_response.xml new file mode 100644 index 00000000..5c992f4e --- /dev/null +++ b/spec/soap_data/get_text_item_response.xml @@ -0,0 +1,7 @@ +NoErrorIPM.NoteRe: Insatisfação totalNormalEm 17/01/2015 16:36, josedasilvasauro@yahoo.com.brO coiso escreveu: +> +> Meu nome é José da Silva Sauro +> CPF.12345678901 +>   Minha reclamação é do coiso em relação a meu pedido +> O que vcs tem pra me informar?? +2015-01-21T13:03:52Z2445Normalfalsefalsefalsefalsetrue2015-01-21T13:03:47Z2015-01-21T13:03:52Zatendimentofalsept-BRfalsefalsefalsetruetruetrueCoiso da Coisa2015-01-21T13:03:52Zfalse?ae=Item&a=Open&t=IPM.Note&id=RgAAAADOrkJOZi%2fnSp0H8kv4afZtBwDDehUq0W%2biQrSvaM5VNEX8AAAAAE5bAACCesYbTRQFQqmH6sMUNPnlAAAAkWs1AAAJ&exvsurl=1Coiso da Coisajosedasilvasauro@yahoo.com.brSMTPOneOffatendimentoatendimento@coisodacoisa.com.brSMTPMailboxfalseAQHQNXqx7tw+D8kT0UGhyXlWEyiYSg==Insatisfação totalCoisa da Coisajosedasilvasauro@yahoo.com.brSMTPOneOff<831821.76477.bm@smtp212.mail.bf1.yahoo.com>trueatendimentoatendimento@coisodacoisa.com.brSMTPMailboxatendimentoatendimento@coisodacoisa.com.brSMTPMailbox diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e54a676f..9739f6f4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,7 +5,10 @@ require 'turn/autorun' require_relative 'xml_matcher' -RSpec.configure do |c| +RSpec.configure do |rspec| + rspec.mock_with :rspec do |mocks| + mocks.yield_receiver_to_any_instance_implementation_blocks = false + end end Turn.config.format = :outline diff --git a/spec/unit/ews_parser_spec.rb b/spec/unit/ews_parser_spec.rb index 0e688c35..3c3a6aea 100644 --- a/spec/unit/ews_parser_spec.rb +++ b/spec/unit/ews_parser_spec.rb @@ -21,4 +21,16 @@ resp.body.should == error_body end + it 'parses a message item with text body response successfully' do + soap_resp = load_soap 'get_text_item', :response + resp = Viewpoint::EWS::SOAP::EwsParser.new(soap_resp).parse + expect(resp.body).to eq([{:get_item_response=>{:elems=>[{:response_messages=>{:elems=>[{:get_item_response_message=>{:elems=>{:response_code=>{:text=>"NoError"}, :items=>{:elems=>[{:message=>{:elems=>[{:item_id=>{:attribs=>{:id=>"AAMkADg4NGVhZGUyLTU2ZTQtNGMwZS1iODRjLWY0YWE2NTYxMGE4ZQBGAAAAAADOrkJOZi/nSp0H8kv4afZtBwDDehUq0W+iQrSvaM5VNEX8AAAAAE5bAACCesYbTRQFQqmH6sMUNPnlAAAAkWs1AAA=", :change_key=>"CQAAABYAAACCesYbTRQFQqmH6sMUNPnlAAAAkdWa"}}}, {:parent_folder_id=>{:attribs=>{:id=>"AAMkADg4NGVhZGUyLTU2ZTQtNGMwZS1iODRjLWY0YWE2NTYxMGE4ZQAuAAAAAADOrkJOZi/nSp0H8kv4afZtAQDDehUq0W+iQrSvaM5VNEX8AAAAAE5bAAA=", :change_key=>"AQAAAA=="}}}, {:item_class=>{:text=>"IPM.Note"}}, {:subject=>{:text=>"Re: Insatisfação total"}}, {:sensitivity=>{:text=>"Normal"}}, {:body=>{:attribs=>{:body_type=>"Text"}, :text=>"Em 17/01/2015 16:36, josedasilvasauro@yahoo.com.brO coiso escreveu:\n\n Meu nome é José da Silva Sauro\n CPF.12345678901\n   Minha reclamação é do coiso em relação a meu pedido\n O que vcs tem pra me informar??\n"}}, {:date_time_received=>{:text=>"2015-01-21T13:03:52Z"}}, {:size=>{:text=>"2445"}}, {:importance=>{:text=>"Normal"}}, {:is_submitted=>{:text=>"false"}}, {:is_draft=>{:text=>"false"}}, {:is_from_me=>{:text=>"false"}}, {:is_resend=>{:text=>"false"}}, {:is_unmodified=>{:text=>"true"}}, {:date_time_sent=>{:text=>"2015-01-21T13:03:47Z"}}, {:date_time_created=>{:text=>"2015-01-21T13:03:52Z"}}, {:response_objects=>{:elems=>[{:reply_to_item=>{}}, {:reply_all_to_item=>{}}, {:forward_item=>{}}]}}, {:display_cc=>{}}, {:display_to=>{:text=>"atendimento"}}, {:has_attachments=>{:text=>"false"}}, {:culture=>{:text=>"pt-BR"}}, {:effective_rights=>{:elems=>[{:create_associated=>{:text=>"false"}}, {:create_contents=>{:text=>"false"}}, {:create_hierarchy=>{:text=>"false"}}, {:delete=>{:text=>"true"}}, {:modify=>{:text=>"true"}}, {:read=>{:text=>"true"}}]}}, {:last_modified_name=>{:text=>"Coiso da Coisa"}}, {:last_modified_time=>{:text=>"2015-01-21T13:03:52Z"}}, {:is_associated=>{:text=>"false"}}, {:web_client_read_form_query_string=>{:text=>"?ae=Itema=Opent=IPM.Noteid=RgAAAADOrkJOZi%2fnSp0H8kv4afZtBwDDehUq0W%2biQrSvaM5VNEX8AAAAAE5bAACCesYbTRQFQqmH6sMUNPnlAAAAkWs1AAAJexvsurl=1"}}, {:conversation_id=>{:attribs=>{:id=>"AAQkADg4NGVhZGUyLTU2ZTQtNGMwZS1iODRjLWY0YWE2NTYxMGE4ZQAQAO7cPg/JE9FBocl5VhMomEo="}}}, {:sender=>{:elems=>[{:mailbox=>{:elems=>[{:name=>{:text=>"Coiso da Coisa"}}, {:email_address=>{:text=>"josedasilvasauro@yahoo.com.br"}}, {:routing_type=>{:text=>"SMTP"}}, {:mailbox_type=>{:text=>"OneOff"}}]}}]}}, {:to_recipients=>{:elems=>[{:mailbox=>{:elems=>[{:name=>{:text=>"atendimento"}}, {:email_address=>{:text=>"atendimento@coisodacoisa.com.br"}}, {:routing_type=>{:text=>"SMTP"}}, {:mailbox_type=>{:text=>"Mailbox"}}]}}]}}, {:is_read_receipt_requested=>{:text=>"false"}}, {:conversation_index=>{:text=>"AQHQNXqx7tw+D8kT0UGhyXlWEyiYSg=="}}, {:conversation_topic=>{:text=>"Insatisfação total"}}, {:from=>{:elems=>[{:mailbox=>{:elems=>[{:name=>{:text=>"Coisa da Coisa"}}, {:email_address=>{:text=>"josedasilvasauro@yahoo.com.br"}}, {:routing_type=>{:text=>"SMTP"}}, {:mailbox_type=>{:text=>"OneOff"}}]}}]}}, {:internet_message_id=>{:text=>"831821.76477.bm@smtp212.mail.bf1.yahoo.com"}}, {:is_read=>{:text=>"true"}}, {:received_by=>{:elems=>[{:mailbox=>{:elems=>[{:name=>{:text=>"atendimento"}}, {:email_address=>{:text=>"atendimento@coisodacoisa.com.br"}}, {:routing_type=>{:text=>"SMTP"}}, {:mailbox_type=>{:text=>"Mailbox"}}]}}]}}, {:received_representing=>{:elems=>[{:mailbox=>{:elems=>[{:name=>{:text=>"atendimento"}}, {:email_address=>{:text=>"atendimento@coisodacoisa.com.br"}}, {:routing_type=>{:text=>"SMTP"}}, {:mailbox_type=>{:text=>"Mailbox"}}]}}]}}]}}]}}, :attribs=>{:response_class=>"Success"}}}]}}]}}]) + end + + it 'parser struct is empty when trying to parse a message item with text body using buggy SAX parser' do + soap_resp = load_soap 'get_text_item', :response + doc = Viewpoint::EWS::SOAP::EwsSaxDocument.new + Nokogiri::XML::SAX::Parser.new(doc).parse soap_resp + expect(doc.struct.empty?).to be true + end end diff --git a/spec/unit/ews_soap_free_busy_response_spec.rb b/spec/unit/ews_soap_free_busy_response_spec.rb index b695a9e5..aa323616 100644 --- a/spec/unit/ews_soap_free_busy_response_spec.rb +++ b/spec/unit/ews_soap_free_busy_response_spec.rb @@ -110,7 +110,7 @@ end it "the calendar_event_array should have one element" do - resp.calendar_event_array.should have(1).items + expect(resp.calendar_event_array.size).to eq 1 end end diff --git a/spec/unit/folder_accessors_spec.rb b/spec/unit/folder_accessors_spec.rb index f118e947..9ece3d12 100644 --- a/spec/unit/folder_accessors_spec.rb +++ b/spec/unit/folder_accessors_spec.rb @@ -46,7 +46,7 @@ def self.folders @ecli.stub(:class_by_name) { cbn } end it '#get_folder should return a Folder' do - @ecli.get_folder(:inbox).should be_instance_of(RSpec::Mocks::Mock) + expect(@ecli.get_folder(:inbox)).to be_a RSpec::Mocks::Double end end end diff --git a/spec/unit/mailbox_accessors_spec.rb b/spec/unit/mailbox_accessors_spec.rb index 329a84be..e9f3e942 100644 --- a/spec/unit/mailbox_accessors_spec.rb +++ b/spec/unit/mailbox_accessors_spec.rb @@ -25,33 +25,29 @@ let(:default_parameters) do { - :start_time => (Time.now - 1).iso8601, - :end_time => Time.now.iso8601, - :requested_view => :detailed + start_time: (Time.now - 1).iso8601, + end_time: Time.now.iso8601, + requested_view: :detailed } end context "#get_user_availability" do it "should care about timezones" do - Viewpoint::EWS::SOAP::ExchangeWebService.any_instance. - should_receive(:do_soap_request) do |request_document| - request_document.at_xpath('//soap:Envelope/soap:Body//t:TimeZone').to_s.should eq timezone_request - end. - and_return(double(:resp, :status => 'Success')) + expect_any_instance_of(Viewpoint::EWS::SOAP::ExchangeWebService).to receive(:do_soap_request) do |request_document| + expect(request_document.at_xpath('//soap:Envelope/soap:Body//t:TimeZone').to_s).to eq timezone_request + end.and_return(double(:resp, status: 'Success')) ecli.get_user_availability( recipients, default_parameters.merge( - :time_zone => { - :bias => 0, - :standard_time => {:bias => 0}, - :daylight_time => {:bias => 0} + time_zone: { + bias: 0, + standard_time: { bias: 0 }, + daylight_time: { bias: 0 } } ) ) end - end - end