├── .bundle └── config ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── TODO.md ├── account.yml.example ├── gmail.gemspec ├── lib ├── gmail.rb └── gmail │ ├── api_resource.rb │ ├── base │ ├── create.rb │ ├── delete.rb │ ├── get.rb │ ├── list.rb │ ├── modify.rb │ ├── trash.rb │ └── update.rb │ ├── draft.rb │ ├── gmail_object.rb │ ├── label.rb │ ├── message.rb │ ├── thread.rb │ ├── util.rb │ └── version.rb └── test ├── gmail ├── api_resource_test.rb ├── draft_test.rb ├── gmail_object_test.rb ├── label_test.rb ├── message_test.rb ├── thread_test.rb └── util_test.rb ├── test_data.rb └── test_helper.rb /.bundle/config: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## rubymine 17 | .idea 18 | 19 | ## PROJECT::GENERAL 20 | coverage 21 | rdoc 22 | pkg 23 | Gemfile.lock 24 | 25 | ## Gem build 26 | *.gem 27 | 28 | ## PROJECT::SPECIFIC 29 | *.rdb 30 | 31 | ## Test Account details 32 | account.yml 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.2.0 5 | - 2.0.0 6 | - 2.1.0 7 | - 2.1.2 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # gmail-api-ruby gem changelog 2 | ## 0.0.1 / 2015-01 3 | 4 | * Birthday! 5 | 6 | ## 0.0.3 / 2015-01-15 7 | 8 | * forward_with, reply_sender_with and reply_all_with won't trigger a deliver but will build the ready to send object 9 | 10 | ## 0.0.4 / 2015-01-15 11 | 12 | * No change, only gem description 13 | 14 | ## 0.0.5 / 2015-01-15 15 | 16 | * updated dependencies 17 | 18 | ## 0.0.6 / 2015-01-16 19 | 20 | * Added insert method for Message and some minor improvements 21 | 22 | ## 0.0.7 / 2015-01-19 23 | 24 | * Fix array empty errors for listing method 25 | 26 | ## 0.0.8 / 2015-01-29 27 | 28 | * minor fix 29 | 30 | ## 0.0.9 / 2015-02-12 31 | 32 | * minor fix 33 | 34 | ## 0.0.10 / 2015-02-17 35 | 36 | * Fixed email parsing in forward and reply method when contact names contain , 37 | 38 | ## 0.0.11 / 2015-06-22 39 | 40 | * Forked to Carpela. Changed auth to work with Service Account 41 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyrignt (c) 2015 Julien Hobeika 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gmail for Ruby 2 | 3 | A Ruby interface to Gmail API: Search, 4 | read and send multipart emails, archive, mark as read/unread, delete emails, 5 | and manage labels. Everything goes through Gmail API (and not through IMAP or SMTP protocols) 6 | 7 | [![Gem Version](https://badge.fury.io/rb/gmail-api-ruby.svg)](http://badge.fury.io/rb/gmail-api-ruby) 8 | [![Build Status](https://travis-ci.org/jhk753/gmail-ruby-api.svg)](https://travis-ci.org/jhk753/gmail-ruby-api) 9 | [![Dependency Status](https://gemnasium.com/jhk753/gmail-ruby-api.svg)](https://gemnasium.com/jhk753/gmail-ruby-api) 10 | 11 | 12 | # Notice 13 | 14 | If for your usecase, the gem is too limited there are two solutions for you: 15 | 16 | 1. Contribute ;) 17 | 2. Look at [Gmail Api doc](https://developers.google.com/gmail/api/v1/reference) to: 18 | * Do custom query by adding parameters I didn't talk about in my doc 19 | * If for some reason I forgot to build some methods, note that you can call fully custom request via Gmail.client (Standard Google Api Client) 20 | 21 | # To Do 22 | 23 | * write proper error handling 24 | 25 | ## Installation 26 | 27 | gem install gmail-api-ruby 28 | 29 | ## Initialization 30 | 31 | ```ruby 32 | Gmail.client_id = "...Your app client id..." or the email from your service account credentials 33 | Gmail.client_secret = "...Your app Secret..." or the pem version of hte api key for service account 34 | Gmail.refresh_token = "...the refresh token of the Gmail account you want to use..." or the account email address for service account auth 35 | # Or for service Accounts 36 | Gmail.auth_method = "service_account" 37 | Gmail.client_id = "... or the email from your service account credentials..." 38 | Gmail.client_secret = "...pem version of the api key ..." 39 | Gmail.email_account = "...the email account you're going to use" 40 | Gmail.auth_scopes = ['http://mail.google.com'] # an array of the permissions required. 41 | ``` 42 | 43 | ## Usage 44 | 45 | ```ruby 46 | require "gmail" 47 | ``` 48 | 49 | ### Common methods 50 | 51 | this works for Messages, Labels, Drafts and Threads (everything) 52 | 53 | ```ruby 54 | Gmail::Message.all 55 | Gmail::Message.all(maxResults: -1) #will parse everything. Use with care. Can through request timeout if used in web apps 56 | Gmail::Message.get(message_id) 57 | some_gmail_message.delete 58 | ``` 59 | 60 | this will work for Messages and Threads 61 | 62 | ```ruby 63 | some_message.archive 64 | some_message.unarchive 65 | some_message.trash 66 | some_message.untrash 67 | some_message.star 68 | some_message.unstar 69 | some_message.mark_as_read 70 | some_message.mark_as_unread 71 | 72 | Gmail::Message.search(in: "labelId" from: "me@gmail.com", to: "you@gmail.com, theothers@gmail.com", subject: "some subject", after: "date", before: "date", has_words: "some words", has_not_words: "some text") 73 | Gmail::Message.search("some words") #search as you would do in gmail interface 74 | 75 | ``` 76 | 77 | 78 | ### Message 79 | 80 | Create a Message object 81 | 82 | ```ruby 83 | m = Gmail::Message.new( 84 | from: "test@test.com", 85 | to: "hello@world.com", 86 | subject: "this is the subject", 87 | body: "this is a text body") 88 | ``` 89 | 90 | If you want a multipart message 91 | 92 | ```ruby 93 | m = Gmail::Message.new( 94 | from: "test@test.com", 95 | to: "hello@world.com", 96 | subject: "this is the subject", 97 | text: "this is a text body", 98 | html: "
this is a html body
", 99 | labelIds: ["cool label", "Newsletter"] #labelIds is always array of labels 100 | ) 101 | ``` 102 | 103 | From this you can create a Draft in Gmail 104 | 105 | ```ruby 106 | m.create_draft 107 | ``` 108 | 109 | or send the Message 110 | 111 | ```ruby 112 | m.deliver 113 | ``` 114 | 115 | or simply insert the message in the user mailbox 116 | 117 | ```ruby 118 | m.insert 119 | ``` 120 | 121 | Notice that the Message object can use from, to, cc, bcc, threadId, labelIds, text, html, body 122 | 123 | If you need to send Message or create draft with custom headers or set other headers. 124 | Or if you need to create a different multipart email, you need to build the "raw" email yourself 125 | 126 | example: 127 | 128 | ```ruby 129 | mail = Mail.new #this is the native ruby Mail object 130 | # 131 | # construct your Mail object 132 | # 133 | raw = Base64.urlsafe_encode64 mail.to_s 134 | m = Gmail::Message.new(raw: raw) 135 | ``` 136 | 137 | In that scenario, you still assign, if needed, ThreadId and labelIds as previously 138 | 139 | ```ruby 140 | m.threadId = "somethreadId" 141 | m.labelIds = ["cool label"] 142 | ``` 143 | 144 | If you want to quickly generate a reply or a forward to message 145 | 146 | ```ruby 147 | # this will construct everything for you, keeping the subject the thread etc as it would happen in Gmail 148 | # reply only to the sender of Message m. 149 | msg = m.reply_sender_with Gmail::Message.new(body: "some reply text") 150 | # reply to all (sender, to (without yourself) and cc) 151 | msg = m.reply_all_with Gmail::Message.new(body: "some reply text") 152 | # forward 153 | msg = m.forward_with Gmail::Message.new(body: "some forward text", to: "address@toforward.com") 154 | # Note that the above will not send the resulting msg 155 | msg.deliver #to send the constructed reply message 156 | 157 | ``` 158 | 159 | Other stuffs that you can do with Message object 160 | 161 | ```ruby 162 | m.detailed #if for any reason Google did send us the full detail of on email object 163 | m.thread #to get the associated Thread 164 | m.inbox? #to know if email is in inbox 165 | m.sent? #to know if email is a sent email 166 | m.unread? #to know if email is unread 167 | ``` 168 | 169 | Those are basics helpers feel free to add more if you want (ex: starred?, important?, etc) 170 | 171 | 172 | ## Thread 173 | 174 | Get a thread 175 | 176 | ```ruby 177 | one_thread = Gmail::Thread.get(thread_id) 178 | ``` 179 | 180 | or 181 | 182 | ```ruby 183 | one_thread = one_message.thread 184 | ``` 185 | 186 | Filter messages in Thread 187 | 188 | ```ruby 189 | one_thread.unread_messages 190 | one_thread.sent_messages 191 | ``` 192 | 193 | Expand messages in Thread 194 | 195 | ```ruby 196 | one_thread.detailed #this will return a thread object but with all messages detailed 197 | one_thread.messages #this will return an array of message objects 198 | ``` 199 | 200 | ## Draft 201 | 202 | As we explained in Message, you create a draft with 203 | 204 | ```ruby 205 | d = some_message.create_draft 206 | ``` 207 | 208 | Modify it with 209 | 210 | ```ruby 211 | d.message.subject = "some different subject than before" 212 | d.save 213 | ``` 214 | 215 | send it with 216 | 217 | ```ruby 218 | d.deliver 219 | ``` 220 | 221 | ## Label 222 | 223 | You can create a label like this: 224 | 225 | ```ruby 226 | l = Gmail::Label.new 227 | l.name = "different Name" 228 | l.messageListVisibility = "hide" #can be 'hide' or 'show' 229 | l.labelListVisibility = "labelShowIfUnread" #can be "labelShowIfUnread", "labelShow", "labelHide" 230 | l.save 231 | ``` 232 | 233 | You can modify a label like this: 234 | 235 | ```ruby 236 | l = Gmail::Label.get("labelId") 237 | l.name = "different Name" 238 | l.messageListVisibility = "hide" 239 | l.labelListVisibility = "labelShowIfUnread" 240 | l.save 241 | ``` 242 | 243 | You can use Labels to directly access box information 244 | 245 | ```ruby 246 | l.detailed #will display number of messages and number of threads if you don't see it 247 | ``` 248 | 249 | For system labels like inbox, sent, trash, important, starred, draft, spam, unread, category_updates, category_promotions, category_social, category_personal, category_forums 250 | 251 | ```ruby 252 | l = Gmail::Label.inbox 253 | ``` 254 | 255 | Access threads and messages from labels 256 | 257 | ```ruby 258 | l.threads 259 | l.messages 260 | 261 | l.unread_threads 262 | l.unread_messages 263 | ``` 264 | 265 | Access threads, drafts and messages with labels 266 | 267 | ```ruby 268 | Gmail::Message.all(labelIds: ["UNREAD", "INBOX"]) 269 | ``` 270 | 271 | ## License 272 | 273 | Copyrignt (c) 2015 Julien Hobeika 274 | 275 | Permission is hereby granted, free of charge, to any person obtaining 276 | a copy of this software and associated documentation files (the 277 | "Software"), to deal in the Software without restriction, including 278 | without limitation the rights to use, copy, modify, merge, publish, 279 | distribute, sublicense, and/or sell copies of the Software, and to 280 | permit persons to whom the Software is furnished to do so, subject to 281 | the following conditions: 282 | 283 | The above copyright notice and this permission notice shall be 284 | included in all copies or substantial portions of the Software. 285 | 286 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 287 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 288 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 289 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 290 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 291 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 292 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | begin 4 | require 'bundler/setup' 5 | rescue LoadError 6 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 7 | end 8 | 9 | begin 10 | require 'rdoc/task' 11 | rescue LoadError 12 | require 'rdoc/rdoc' 13 | require 'rake/rdoctask' 14 | RDoc::Task = Rake::RDocTask 15 | end 16 | 17 | RDoc::Task.new(:rdoc) do |rdoc| 18 | rdoc.rdoc_dir = 'rdoc' 19 | rdoc.title = "Gmail API for Ruby #{Gmail::VERSION}" 20 | rdoc.rdoc_files.include('README*') 21 | rdoc.rdoc_files.include('lib/**/*.rb') 22 | end 23 | 24 | Bundler::GemHelper.install_tasks 25 | 26 | require 'rake/testtask' 27 | 28 | task :default => [:test] 29 | 30 | Rake::TestTask.new do |t| 31 | t.pattern = './test/**/*_test.rb' 32 | end -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | *proper errors handling -------------------------------------------------------------------------------- /account.yml.example: -------------------------------------------------------------------------------- 1 | # Auth Method 2 | google_api_auth_method: ENV['GOOGLE_API_AUTH_METHOD'] 3 | 4 | #App Info 5 | application_name: ENV['APP_NAME'] 6 | application_version: ENV['APP_VERSION'] 7 | 8 | # Web Application Auth 9 | client_id: ENV['GOOGLE_API_CLIENT_ID'] 10 | client_secret: ENV['GOOGLE_API_CLIENT_SECRET'] 11 | refresh_token: ENV['GOOGLE_API_REFRESH_TOKEN'] 12 | 13 | # Service Account Auth 14 | google_api_key: ENV['GOOGLE_API_KEY'] 15 | google_api_email: ENV['GOOGLE_API_EMAIL'] 16 | email_account: ENV['GOOGLE_ACCOUNT'] 17 | auth_scopes: ENV['GOOGLE_AUTH_SCOPES'] -------------------------------------------------------------------------------- /gmail.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | $:.push File.expand_path('../lib', __FILE__) 4 | require 'gmail/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "gmail-api-ruby" 8 | s.summary = "A Ruby interface to Gmail API (NO IMAP, NO SMTP), with all the tools you will need." 9 | s.description = "A Ruby interface to Gmail API (NO IMAP, NO SMTP). 10 | Search, read and send multipart emails; archive, mark as read/unread, 11 | delete emails; and manage labels. Everything is done through the Gmail API without going through IMAP or SMTP Protocol 12 | " 13 | s.version = Gmail::VERSION 14 | s.platform = Gem::Platform::RUBY 15 | s.authors = ["Julien Hobeika","Keiran Betteley"] 16 | s.homepage = "http://github.com/jhk753/gmail-ruby-api" 17 | s.licenses = ['MIT'] 18 | 19 | # runtime dependencies 20 | s.required_ruby_version = '>= 1.9.3' 21 | s.add_dependency "mime", ">= 0.1" 22 | s.add_dependency "mail", ">= 2.2.1" 23 | s.add_dependency "activesupport" 24 | s.add_dependency 'google-api-client', "0.8.6" 25 | s.add_dependency "hooks", ">=0.4.0" 26 | s.add_dependency "hashie", ">=3.3.2" 27 | s.add_dependency "stringex" 28 | 29 | # development dependencies 30 | s.add_development_dependency "rake" 31 | s.add_development_dependency "test-unit" 32 | s.add_development_dependency('mocha', '~> 1.0.0') 33 | s.add_development_dependency('shoulda', '~> 3.5.0') 34 | 35 | 36 | s.add_development_dependency "gem-release" 37 | 38 | s.files = `git ls-files`.split("\n") 39 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 40 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 41 | s.require_paths = ["lib"] 42 | end 43 | -------------------------------------------------------------------------------- /lib/gmail.rb: -------------------------------------------------------------------------------- 1 | require 'mail' 2 | require 'hashie' 3 | require 'hooks' 4 | require 'google/api_client' 5 | require 'gmail/gmail_object' 6 | require 'gmail/api_resource' 7 | #base 8 | require 'gmail/base/create' 9 | require 'gmail/base/delete' 10 | require 'gmail/base/get' 11 | require 'gmail/base/list' 12 | require 'gmail/base/update' 13 | require 'gmail/base/modify' 14 | require 'gmail/base/trash' 15 | 16 | #object 17 | require 'gmail/util' 18 | require 'gmail/message' 19 | require 'gmail/draft' 20 | require 'gmail/thread' 21 | require 'gmail/label' 22 | 23 | module Gmail 24 | 25 | class << self 26 | attr_accessor :auth_method, :client_id, :client_secret, 27 | :refresh_token, :auth_scopes, :email_account, :application_name, :application_version 28 | attr_reader :service, :client, :mailbox_email 29 | def new hash 30 | [:auth_method, :client_id, :client_secret, :refresh_token, :auth_scopes, :email_account, :application_name, :application_version].each do |accessor| 31 | Gmail.send("#{accessor}=", hash[accessor.to_s]) 32 | end 33 | end 34 | end 35 | 36 | Google::APIClient.logger.level = 3 37 | @service = Google::APIClient.new.discovered_api('gmail', 'v1') 38 | Google::APIClient.logger.level = 2 39 | 40 | begin 41 | Gmail.new YAML.load_file("account.yml") # for development purpose 42 | rescue 43 | 44 | end 45 | 46 | def self.request(method, params={}, body={}, auth_method=@auth_method) 47 | 48 | params[:userId] ||= "me" 49 | case auth_method 50 | when "web_application" 51 | if @client.nil? 52 | self.connect 53 | end 54 | when "service_account" 55 | if @client.nil? 56 | self.service_account_connect 57 | elsif self.client.authorization.principal != @email_account 58 | self.service_account_connect 59 | end 60 | end 61 | 62 | if body.empty? 63 | response = @client.execute( 64 | :api_method => method, 65 | :parameters => params, 66 | 67 | :headers => {'Content-Type' => 'application/json'}) 68 | else 69 | 70 | response = @client.execute( 71 | :api_method => method, 72 | :parameters => params, 73 | :body_object => body, 74 | :headers => {'Content-Type' => 'application/json'}) 75 | end 76 | parse(response) 77 | 78 | end 79 | 80 | def self.mailbox_email 81 | @mailbox_email ||= self.request(@service.users.to_h['gmail.users.getProfile'])[:emailAddress] 82 | end 83 | 84 | 85 | 86 | def self.connect(client_id=@client_id, client_secret=@client_secret, refresh_token=@refresh_token) 87 | unless client_id 88 | raise 'No client_id specified' 89 | end 90 | 91 | unless client_secret 92 | raise 'No client_secret specified' 93 | end 94 | 95 | unless refresh_token 96 | raise 'No refresh_token specified' 97 | end 98 | 99 | @client = Google::APIClient.new( 100 | application_name: @application_name, 101 | application_version: @application_version 102 | ) 103 | @client.authorization.client_id = client_id 104 | @client.authorization.client_secret = client_secret 105 | @client.authorization.refresh_token = refresh_token 106 | @client.authorization.grant_type = 'refresh_token' 107 | @client.authorization.fetch_access_token! 108 | @client.auto_refresh_token = true 109 | 110 | #@service = @client.discovered_api('gmail', 'v1') 111 | 112 | end 113 | 114 | def self.service_account_connect( 115 | client_id=@client_id, client_secret=@client_secret, 116 | email_account=@email_account, auth_scopes=@auth_scopes, 117 | application_name=@application_name, application_version=@application_version 118 | ) 119 | puts "Authenticating service account - #{email_account}" 120 | 121 | 122 | @client = Google::APIClient.new(application_name: application_name, application_version: application_version) 123 | 124 | 125 | 126 | key = Google::APIClient::KeyUtils.load_from_pem( 127 | client_secret, 128 | 'notasecret') 129 | asserter = Google::APIClient::JWTAsserter.new( 130 | client_id, 131 | auth_scopes, 132 | key 133 | ) 134 | @client.authorization = asserter.authorize(email_account) 135 | 136 | end 137 | 138 | def self.parse(response) 139 | begin 140 | 141 | if response.body.empty? 142 | return response.body 143 | else 144 | response = JSON.parse(response.body) 145 | end 146 | 147 | rescue JSON::ParserError 148 | raise "error code: #{response.error},body: #{response.body})" 149 | end 150 | 151 | r = Gmail::Util.symbolize_names(response) 152 | if r[:error] 153 | raise "#{r[:error]}" 154 | end 155 | r 156 | end 157 | 158 | 159 | end # Gmail 160 | -------------------------------------------------------------------------------- /lib/gmail/api_resource.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | class APIResource < GmailObject 3 | def self.class_name 4 | self.name.split('::')[-1] 5 | end 6 | 7 | def self.base_method 8 | Gmail.service.users.send("#{class_name.downcase}s") 9 | end 10 | 11 | end 12 | end -------------------------------------------------------------------------------- /lib/gmail/base/create.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module Create 4 | module ClassMethods 5 | def create(body, opts={}) 6 | response = Gmail.request(base_method.send("create"), {}, body) 7 | Util.convert_to_gmail_object(response, class_name.downcase) 8 | end 9 | end 10 | 11 | def self.included(base) 12 | base.extend(ClassMethods) 13 | end 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /lib/gmail/base/delete.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module Delete 4 | def delete(opts={}) 5 | response = Gmail.request(self.class.base_method.send("delete"),{id: id}) 6 | if response == "" 7 | true 8 | else 9 | false 10 | end 11 | end 12 | 13 | module ClassMethods 14 | def delete(id, opts={}) 15 | response = Gmail.request(base_method.send("delete"),{id: id}) 16 | if response == "" 17 | true 18 | else 19 | false 20 | end 21 | end 22 | end 23 | 24 | def self.included(base) 25 | base.extend(ClassMethods) 26 | end 27 | 28 | 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/gmail/base/get.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module Get 4 | module ClassMethods 5 | def get(id) 6 | response = Gmail.request(base_method.send("get"), {id: id}) 7 | Util.convert_to_gmail_object(response, class_name.downcase) 8 | 9 | end 10 | end 11 | 12 | def self.included(base) 13 | base.extend(ClassMethods) 14 | end 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/gmail/base/list.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module List 4 | module ClassMethods 5 | def all(filters={}, opts={}) 6 | max_results = filters[:maxResults] || 100 7 | opts[:items] ||= [] 8 | 9 | if max_results == -1 10 | filters.merge!({maxResults: 100}) 11 | end 12 | response = Gmail.request(base_method.send("list"), filters) 13 | items = response["#{class_name.downcase}s".to_sym] || [] 14 | next_page_token = response[:nextPageToken] 15 | opts[:items] = opts[:items] + items 16 | 17 | if items.count < 100 || items.count < max_results 18 | Util.convert_to_gmail_object(opts[:items], class_name.downcase) 19 | else 20 | max_results = (max_results == -1)?-1:max_results-items.count 21 | all(filters.merge({maxResults: max_results, pageToken: next_page_token}), opts) 22 | end 23 | end 24 | 25 | def search(q={}) 26 | if q.is_a? String 27 | all({q: q}) 28 | else 29 | query = "" 30 | [:from, :to, :subject].each do |prop| 31 | query += "#{prop.to_s}:(#{q[prop].downcase}) " unless q[prop].nil? 32 | q.delete(prop) 33 | end 34 | [:in, :before, :after].each do |prop| 35 | query += "#{prop.to_s}:#{q[prop]} " unless q[prop].nil? 36 | q.delete(prop) 37 | end 38 | 39 | query += "#{q[:has_words]} " unless q[:has_words].nil? 40 | query += "-{#{q[:has_not_words]}}" unless q[:has_not_words].nil? 41 | q.delete(:has_words) 42 | q.delete(:has_not_words) 43 | 44 | all(q.merge({q: query})) 45 | end 46 | end 47 | end 48 | 49 | def self.included(base) 50 | base.extend(ClassMethods) 51 | end 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /lib/gmail/base/modify.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module Modify 4 | def modify!(addLabelIds=[], removeLabelIds=[]) 5 | response = Gmail.request(self.class.base_method.send("modify"),{id: id}, {addLabelIds: addLabelIds, removeLabelIds: removeLabelIds}) 6 | d = Util.convert_to_gmail_object(response, self.class.class_name.downcase) 7 | @values = d.values 8 | self 9 | end 10 | 11 | def modify(addLabelIds=[], removeLabelIds=[]) 12 | response = Gmail.request(self.class.base_method.send("modify"),{id: id}, {addLabelIds: addLabelIds, removeLabelIds: removeLabelIds}) 13 | d = Util.convert_to_gmail_object(response, self.class.class_name.downcase) 14 | end 15 | 16 | def archive 17 | modify([], ["INBOX"]) 18 | end 19 | 20 | def archive! 21 | modify!([], ["INBOX"]) 22 | end 23 | 24 | def unarchive 25 | modify(["INBOX"], [] ) 26 | end 27 | 28 | def unarchive! 29 | modify!(["INBOX"], [] ) 30 | end 31 | 32 | def star 33 | modify(["STARRED"], [] ) 34 | end 35 | 36 | def star! 37 | modify!(["STARRED"], [] ) 38 | end 39 | 40 | def unstar 41 | modify([],["STARRED"] ) 42 | end 43 | 44 | def unstar! 45 | modify!([],["STARRED"] ) 46 | end 47 | 48 | def mark_as_read 49 | modify([],["UNREAD"] ) 50 | end 51 | 52 | def mark_as_read! 53 | modify!([],["UNREAD"] ) 54 | end 55 | 56 | def mark_as_unread 57 | modify(["UNREAD"],[] ) 58 | end 59 | 60 | def mark_as_unread! 61 | modify!(["UNREAD"],[] ) 62 | end 63 | 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /lib/gmail/base/trash.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module Trash 4 | def trash(opts={}) 5 | response = Gmail.request(self.class.base_method.send("trash"),{id: id}) 6 | Util.convert_to_gmail_object(response, self.class.class_name.downcase) 7 | end 8 | 9 | def untrash(opts={}) 10 | response = Gmail.request(self.class.base_method.send("untrash"),{id: id}) 11 | Util.convert_to_gmail_object(response, self.class.class_name.downcase) 12 | end 13 | 14 | module ClassMethods 15 | def trash(id, opts={}) 16 | response = Gmail.request(base_method.send("trash"),{id: id}) 17 | Util.convert_to_gmail_object(response, class_name.downcase) 18 | end 19 | 20 | def untrash(id, opts={}) 21 | response = Gmail.request(base_method.send("untrash"),{id: id}) 22 | Util.convert_to_gmail_object(response, class_name.downcase) 23 | end 24 | end 25 | 26 | def self.included(base) 27 | base.extend(ClassMethods) 28 | end 29 | 30 | 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /lib/gmail/base/update.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Base 3 | module Update 4 | def update!(body) 5 | if id.nil? 6 | d = self.class.create(body) 7 | else 8 | response = Gmail.request(self.class.base_method.send("update"),{id: id}, body) 9 | d = Util.convert_to_gmail_object(response, self.class.class_name.downcase) 10 | end 11 | @values = d.values 12 | self 13 | end 14 | 15 | def update(body) 16 | if id.nil? 17 | d = self.class.create(body) 18 | else 19 | response = Gmail.request(self.class.base_method.send("update"),{id: id}, body) 20 | d = Util.convert_to_gmail_object(response, self.class.class_name.downcase) 21 | end 22 | d 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/gmail/draft.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | class Draft < APIResource 3 | include Base::List 4 | include Base::Create 5 | include Base::Delete 6 | include Base::Get 7 | include Base::Update 8 | 9 | def message 10 | if @values.message.is_a?(Message) 11 | @values.message 12 | else 13 | @values.message = Util.convert_to_gmail_object(to_hash[:message], key="message") 14 | if @values.message.payload.nil? 15 | self.detailed! 16 | message 17 | end 18 | @values.message 19 | end 20 | end 21 | 22 | def save(opts={}) 23 | msg = {raw: message.raw} 24 | if message.threadId 25 | msg[:threadId] = message.threadId 26 | end 27 | if message.labelIds 28 | msg[:labelIds] = message.labelIds 29 | end 30 | body = {message: msg} 31 | update(body) 32 | end 33 | 34 | def save!(opts={}) 35 | msg = {raw: message.raw} 36 | if message.threadId 37 | msg[:threadId] = message.threadId 38 | end 39 | if message.labelIds 40 | msg[:labelIds] = message.labelIds 41 | end 42 | body = {message: msg} 43 | update!(body) 44 | end 45 | 46 | def deliver 47 | response = Gmail.request(self.class.base_method.to_h['gmail.users.drafts.send'],{},{id: id}) 48 | Message.get(response[:id]) 49 | end 50 | 51 | 52 | 53 | end 54 | end -------------------------------------------------------------------------------- /lib/gmail/gmail_object.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | class GmailObject 3 | include Enumerable 4 | include Hooks 5 | define_hooks :after_initialize 6 | 7 | # The default :id method is deprecated and isn't useful to us 8 | if method_defined?(:id) 9 | undef :id 10 | end 11 | 12 | def initialize(hash={}) 13 | @values = Hashie::Mash.new hash 14 | run_hook :after_initialize 15 | end 16 | 17 | 18 | def to_s(*args) 19 | JSON.pretty_generate(@values.to_hash) 20 | end 21 | 22 | def inspect 23 | "#<#{self.class}:0x#{self.object_id.to_s(16)}> " + to_s 24 | end 25 | 26 | def [](k) 27 | @values[k.to_sym] 28 | end 29 | 30 | def []=(k, v) 31 | @values.send("#{k}=", v) 32 | end 33 | 34 | def to_json(*a) 35 | JSON.generate(@values) 36 | end 37 | 38 | def as_json(*a) 39 | @values.as_json(*a) 40 | end 41 | 42 | def detailed 43 | if self.class == GmailObject 44 | raise "Can't detail a generic GmailObject. It needs to be a Thread, Message, Draft or Label" 45 | end 46 | 47 | self.class.get(id) 48 | end 49 | 50 | def refresh 51 | if self.class == GmailObject 52 | raise "Can't refresh a generic GmailObject. It needs to be a Thread, Message, Draft or Label" 53 | end 54 | @values = self.class.get(id).values 55 | self 56 | end 57 | 58 | alias_method :detailed!, :refresh 59 | # 60 | def to_hash 61 | Util.symbolize_names(@values.to_hash) 62 | end 63 | 64 | def values 65 | @values 66 | end 67 | # 68 | # def each(&blk) 69 | # @values.each(&blk) 70 | # end 71 | # 72 | # def _dump(level) 73 | # Marshal.dump([@values, @api_key]) 74 | # end 75 | # 76 | # def self._load(args) 77 | # values, api_key = Marshal.load(args) 78 | # construct_from(values) 79 | # end 80 | # 81 | # if RUBY_VERSION < '1.9.2' 82 | # def respond_to?(symbol) 83 | # @values.has_key?(symbol) || super 84 | # end 85 | # end 86 | # 87 | protected 88 | 89 | def metaclass 90 | class << self; self; end 91 | end 92 | # 93 | # def remove_accessors(keys) 94 | # metaclass.instance_eval do 95 | # keys.each do |k| 96 | # next if @@permanent_attributes.include?(k) 97 | # k_eq = :"#{k}=" 98 | # remove_method(k) if method_defined?(k) 99 | # remove_method(k_eq) if method_defined?(k_eq) 100 | # end 101 | # end 102 | # end 103 | # 104 | # def add_accessors(keys) 105 | # metaclass.instance_eval do 106 | # keys.each do |k| 107 | # next if @@permanent_attributes.include?(k) 108 | # k_eq = :"#{k}=" 109 | # define_method(k) { @values[k] } 110 | # define_method(k_eq) do |v| 111 | # if v == "" 112 | # raise ArgumentError.new( 113 | # "You cannot set #{k} to an empty string." + 114 | # "We interpret empty strings as nil in requests." + 115 | # "You may set #{self}.#{k} = nil to delete the property.") 116 | # end 117 | # @values[k] = v 118 | # @unsaved_values.add(k) 119 | # end 120 | # end 121 | # end 122 | # end 123 | # 124 | def method_missing(name, *args) 125 | 126 | if @values.send(name.to_s + "?") 127 | @values.send(name) 128 | else 129 | begin 130 | @values.send(name.to_s, args[0]) 131 | rescue 132 | begin 133 | super.send(name) 134 | rescue 135 | nil 136 | end 137 | end 138 | end 139 | end 140 | 141 | 142 | # 143 | # def respond_to_missing?(symbol, include_private = false) 144 | # super 145 | # end 146 | end 147 | end -------------------------------------------------------------------------------- /lib/gmail/label.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | class Label < APIResource 3 | include Base::List 4 | include Base::Create 5 | include Base::Delete 6 | include Base::Get 7 | include Base::Update 8 | 9 | def save 10 | update(to_hash) 11 | end 12 | 13 | def save! 14 | update!(to_hash) 15 | end 16 | 17 | def self.boxes 18 | @boxes ||= [:inbox, :sent, :trash, :important, :starred, :draft, :spam, :unread, :category_updates, :category_promotions, :category_social, :category_personal, :category_forums ] 19 | end 20 | 21 | boxes.each do |method| 22 | define_singleton_method method do 23 | Label.get(method.to_s.upcase) 24 | end 25 | end 26 | 27 | def messages filters={} 28 | filters = {labelIds: [id]}.merge(filters) 29 | filters[:labelIds] = filters[:labelIds] | [id] 30 | Message.all(filters) 31 | end 32 | 33 | def unread_messages 34 | if messagesUnread == 0 35 | [] 36 | else 37 | Message.all({labelIds: [id, "UNREAD"]}) 38 | end 39 | end 40 | 41 | def threads filters={} 42 | filters = {labelIds: [id]}.merge(filters) 43 | filters[:labelIds] = filters[:labelIds] | [id] 44 | Thread.all(filters) 45 | end 46 | 47 | def unread_threads 48 | Thread.all({labelIds: [id, "UNREAD"]}) 49 | end 50 | 51 | end 52 | end # Gmail 53 | -------------------------------------------------------------------------------- /lib/gmail/message.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Gmail 3 | class Message < APIResource 4 | include Base::List 5 | include Base::Delete 6 | include Base::Get 7 | include Base::Modify 8 | include Base::Trash 9 | 10 | require "stringex" 11 | 12 | after_initialize :set_basics 13 | 14 | def thread 15 | Thread.get(threadId) 16 | end 17 | 18 | def create_draft 19 | Draft.create(message: msg_parameters) 20 | end 21 | 22 | def deliver! 23 | response = Gmail.request(self.class.base_method.to_h['gmail.users.messages.send'],{}, msg_parameters) 24 | @values = Message.get(response[:id]).values 25 | self 26 | end 27 | 28 | def deliver 29 | response = Gmail.request(self.class.base_method.to_h['gmail.users.messages.send'],{}, msg_parameters) 30 | Message.get(response[:id]) 31 | end 32 | 33 | def insert 34 | response = Gmail.request(self.class.base_method.insert,{}, msg_parameters) 35 | Message.get(response[:id]) 36 | end 37 | 38 | def insert! 39 | response = Gmail.request(self.class.base_method.insert,{}, msg_parameters) 40 | @values = Message.get(response[:id]).values 41 | self 42 | end 43 | 44 | def reply_all_with msg 45 | msg = set_headers_for_reply msg 46 | msg = quote_in msg 47 | msg 48 | end 49 | 50 | def reply_sender_with msg 51 | msg = set_headers_for_reply msg 52 | msg = quote_in msg 53 | msg.cc = nil 54 | msg 55 | end 56 | 57 | def forward_with msg 58 | # save headers that need to be override by users compared to a classic reply 59 | x_cc = msg.cc 60 | x_to = msg.to 61 | x_bcc = msg.bcc 62 | x_subject = msg.subject || subject #if user doesn't override keep classic behavior 63 | # set headers as for reply 64 | msg = set_headers_for_reply msg 65 | # quote message 66 | msg = quote_in msg 67 | # reset saved overridden headers 68 | msg.cc = x_cc 69 | msg.to = x_to 70 | msg.bcc = x_bcc 71 | msg.subject = x_subject 72 | msg 73 | end 74 | 75 | 76 | def thread_id 77 | threadId 78 | end 79 | 80 | 81 | def unread? 82 | (labelIds||[]).include?("UNREAD") 83 | end 84 | 85 | def sent? 86 | (labelIds||[]).include?("SENT") 87 | end 88 | 89 | def inbox? 90 | (labelIds||[]).include?("INBOX") 91 | end 92 | 93 | 94 | 95 | def raw # is not in private because the method is used in Draft 96 | if super #check if raw is set to allow fully custom message to be sent 97 | super 98 | else 99 | s = self 100 | msg = Mail.new 101 | msg.subject = subject 102 | if body 103 | msg.body = body 104 | end 105 | msg.from = from 106 | msg.to = to 107 | msg.cc = cc 108 | msg.header['X-Bcc'] = bcc unless bcc.nil?#because Mail gem doesn't allow bcc headers... 109 | msg.in_reply_to = in_reply_to unless in_reply_to.nil? 110 | msg.references = references unless references.nil? 111 | if text || html 112 | bodypart = Mail::Part.new 113 | if text 114 | bodypart.text_part = Mail::Part.new do |p| 115 | content_type 'text/plain; charset=UTF-8' 116 | p.body s.text 117 | end 118 | end 119 | if html 120 | bodypart.html_part = Mail::Part.new do |p| 121 | content_type 'text/html; charset=UTF-8' 122 | p.body s.html 123 | end 124 | end 125 | msg.add_part bodypart 126 | end 127 | if attachments 128 | if attachments.is_a?(Hash) 129 | attachments.each do |name, attachment| 130 | msg.add_file filename: name, content: attachment 131 | end 132 | elsif attachments.is_a?(Array) 133 | attachments.each do |attachment| 134 | msg.add_file(attachment) 135 | end 136 | end 137 | end 138 | Base64.urlsafe_encode64 msg.to_s.sub("X-Bcc", "Bcc") #because Mail gem doesn't allow bcc headers... 139 | end 140 | end 141 | 142 | private 143 | 144 | def msg_parameters 145 | msg = {raw: raw} 146 | if threadId 147 | msg[:threadId] = threadId 148 | end 149 | if labelIds 150 | msg[:labelIds] = labelIds 151 | end 152 | msg 153 | end 154 | 155 | def self.find_addresses str 156 | Mail::AddressList.new("#{str}".to_ascii.gsub(/<(<(.)*@(.)*>)(.)*>/, '\1')) 157 | end 158 | 159 | def set_headers_for_reply msg 160 | #to_ar = [] 161 | #split_regexp = Regexp.new("\s*,\s*") 162 | own_email = delivered_to || Gmail.mailbox_email 163 | 164 | 165 | to_ar = (Message.find_addresses(to).addresses + Message.find_addresses(cc).addresses).map(&:to_s) 166 | #to_ar = (to || "").split(split_regexp) + (cc || "").split(split_regexp) 167 | result = to_ar.grep(Regexp.new(own_email, "i")) 168 | to_ar = to_ar - result 169 | 170 | msg.subject = subject 171 | if from.match(Regexp.new(own_email, "i")) 172 | msg.to = to_ar.first 173 | to_ar = to_ar.drop(1) 174 | else 175 | msg.to = from 176 | end 177 | msg.cc = to_ar.join(", ") 178 | msg.bcc = nil 179 | msg.threadId = thread_id 180 | msg.references = ((references || "").split(Regexp.new "\s+") << message_id).join(" ") 181 | msg.in_reply_to = ((in_reply_to || "").split(Regexp.new "\s+") << message_id).join(" ") 182 | msg 183 | end 184 | 185 | def quote_in reply_msg 186 | text_to_append = "\r\n\r\n#{date} #{from}:\r\n\r\n>" + (body || text).gsub("\n", "\n>") unless body.nil? && text.nil? 187 | html_to_append = "\r\n

#{date} #{CGI.escapeHTML(from)}:
" + html + "

" unless html.nil? 188 | reply_msg.html = "
" + reply_msg.html + "
" + html_to_append unless reply_msg.html.nil? 189 | reply_msg.text = reply_msg.text + text_to_append unless reply_msg.text.nil? 190 | reply_msg.body = reply_msg.body + text_to_append unless reply_msg.body.nil? 191 | reply_msg 192 | end 193 | 194 | def urlsafe_decode64 code 195 | Base64.urlsafe_decode64(code).force_encoding('UTF-8').encode 196 | end 197 | 198 | 199 | def set_basics 200 | if @values.payload 201 | ["From", "To", "Cc", "Subject", "Bcc", "Date", "Message-ID", "References", "In-Reply-To", "Delivered-To"].each do |n| 202 | if payload_n = @values.payload.headers.select{|h| h.name.downcase == n.downcase}.first 203 | @values.send(n.downcase.tr("-", "_") + "=", payload_n.value) 204 | end 205 | end 206 | 207 | if payload.parts 208 | content_payload = @values.payload.find_all_object_containing("mimeType", "multipart/alternative").first 209 | content_payload ||= @values.payload 210 | text_part=content_payload.find_all_object_containing("mimeType", "text/plain").first 211 | if text_part && text_part.body.data 212 | @values.text = urlsafe_decode64(text_part.body.data) 213 | end 214 | html_part=content_payload.find_all_object_containing("mimeType", "text/html").first 215 | if html_part && html_part.body.data 216 | @values.html = urlsafe_decode64(html_part.body.data) 217 | end 218 | end 219 | if payload.body.data 220 | @values.body = urlsafe_decode64(@values.payload.body.data) 221 | end 222 | end 223 | end 224 | 225 | class Hashie::Mash 226 | def find_all_object_containing(key, value ) 227 | result=[] 228 | if self.send(key) == value 229 | result << self 230 | end 231 | self.values.each do |vs| 232 | vs = [vs] unless vs.is_a? Array 233 | vs.each do |v| 234 | result += v.find_all_object_containing(key,value) if v.is_a? Hashie::Mash 235 | end 236 | end 237 | result 238 | end 239 | end 240 | 241 | end 242 | end -------------------------------------------------------------------------------- /lib/gmail/thread.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | class Thread < APIResource 3 | include Base::List 4 | include Base::Delete 5 | include Base::Get 6 | include Base::Modify 7 | include Base::Trash 8 | 9 | def messages 10 | 11 | if @values.messages.is_a? Array 12 | if @values.messages.first.is_a? Message 13 | @values.messages 14 | else 15 | @values.messages = Util.convert_to_gmail_object(to_hash[:messages], key="message") 16 | end 17 | else 18 | self.detailed! 19 | messages 20 | end 21 | 22 | end 23 | 24 | def unread_messages 25 | 26 | messages.select{|m| m.unread?} 27 | 28 | end 29 | 30 | 31 | def sent_messages 32 | 33 | messages.select{|m| m.sent?} 34 | 35 | end 36 | 37 | end 38 | end -------------------------------------------------------------------------------- /lib/gmail/util.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | module Util 3 | 4 | def self.object_classes 5 | @object_classes ||= { 6 | # data structures 7 | 8 | # business objects 9 | 'draft' => Draft, 10 | 'label' => Label, 11 | 'message' => Message, 12 | 'thread' => Thread 13 | } 14 | end 15 | 16 | def self.convert_to_gmail_object(resp, key=nil) 17 | case resp 18 | when Array 19 | resp.map { |i| convert_to_gmail_object(i, key) } 20 | when Hash 21 | # Try converting to a known object class. If none available, fall back to generic StripeObject 22 | object_classes.fetch(key , GmailObject).new(resp) 23 | else 24 | resp 25 | end 26 | end 27 | 28 | 29 | def self.symbolize_names(object) 30 | case object 31 | when Hash 32 | new_hash = {} 33 | object.each do |key, value| 34 | key = (key.to_sym rescue key) || key 35 | new_hash[key] = symbolize_names(value) 36 | end 37 | new_hash 38 | when Array 39 | object.map { |value| symbolize_names(value) } 40 | else 41 | object 42 | end 43 | end 44 | end 45 | end -------------------------------------------------------------------------------- /lib/gmail/version.rb: -------------------------------------------------------------------------------- 1 | module Gmail 2 | VERSION = "0.0.14" 3 | end # Gmail 4 | -------------------------------------------------------------------------------- /test/gmail/api_resource_test.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path('../../test_helper', __FILE__) 3 | 4 | module Gmail 5 | class ApiResourceTest < Test::Unit::TestCase 6 | 7 | should "creating a new APIResource should not fetch over the network" do 8 | @mock.expects(:execute).never 9 | Gmail::Label.new({ 10 | name: "test" 11 | }) 12 | end 13 | 14 | should "setting an attribute should not cause a network request" do 15 | @mock.expects(:execute).never 16 | m = Gmail::Message.new({subject: "test"}) 17 | m.body = "this is a test body" 18 | end 19 | 20 | should "accessing id should not issue a fetch" do 21 | @mock.expects(:execute).never 22 | c = Gmail::Message.new({subject: "test"}) 23 | c.id 24 | end 25 | 26 | should "construct URL properly with base query parameters" do 27 | response = test_response(test_thread_list) 28 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).returns(response) 29 | Gmail::Thread.all 30 | 31 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {maxResults: 150, userId: "test@test.com"}, headers: {'Content-Type' => 'application/json'}).returns(response) 32 | Gmail::Thread.all(maxResults: 150, userId: "test@test.com") 33 | end 34 | 35 | 36 | should "deleting should return true" do 37 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.delete, parameters: {userId: "me", id: test_draft[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response("")) 38 | 39 | d = Gmail::Draft.new test_draft 40 | 41 | assert_equal true, d.delete 42 | 43 | end 44 | 45 | 46 | end 47 | end -------------------------------------------------------------------------------- /test/gmail/draft_test.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path('../../test_helper', __FILE__) 3 | 4 | module Gmail 5 | class DraftTest < Test::Unit::TestCase 6 | 7 | should "Draft should be retrievable by id" do 8 | 9 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.get, parameters: {userId: "me", id: test_draft[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_draft)) 10 | d = Gmail::Draft.get(test_draft[:id]) 11 | assert d.kind_of?Gmail::Draft 12 | assert_equal test_draft[:id], d.id 13 | end 14 | 15 | should "drafts should be listable" do 16 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_draft_list)) 17 | list = Gmail::Draft.all 18 | assert list.kind_of? Array 19 | assert list[0].kind_of? Gmail::Draft 20 | end 21 | 22 | context "Message Object in draft" do 23 | should "retrieved Draft should not generate call to get Message Object" do 24 | draft = Gmail::Draft.new(test_draft) 25 | @mock.expects(:execute).never 26 | assert draft.message.kind_of?Gmail::Message 27 | end 28 | 29 | should "Draft get from a draft list should generate call to get Message Object" do 30 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_draft_list)) 31 | list = Gmail::Draft.all 32 | draft = list.first 33 | 34 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.get, parameters: {userId: "me", id: test_draft[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_draft)) 35 | 36 | assert draft.message.kind_of?Gmail::Message 37 | assert_not_nil draft.message.payload 38 | end 39 | end 40 | 41 | 42 | should "drafts should be deletable" do 43 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.delete, parameters: {userId: "me", id: test_draft[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response("")) 44 | d = Gmail::Draft.new(test_draft) 45 | r = d.delete 46 | assert r 47 | end 48 | 49 | should "drafts should be updateable" do 50 | draft_hash = test_draft 51 | draft_hash[:message].merge!({labelIds: ["COOL LABEL"]}) 52 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.get, parameters: {id: test_draft[:id], userId: "me"} , headers: {'Content-Type' => 'application/json'}).once.returns(test_response(draft_hash)) 53 | 54 | d = Gmail::Draft.new(id: test_draft[:id]).detailed 55 | # those two lines are required because raw generation change between two calls... 56 | raw = d.message.raw 57 | d.message.raw = raw 58 | ### 59 | assert_equal ["COOL LABEL"], d.message.labelIds 60 | 61 | draft_hash[:message].merge!({labelIds: ["INBOX"]}) 62 | 63 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.update, parameters: {id: test_draft[:id], userId: "me"}, body_object:{message: {raw: d.message.raw, threadId: test_draft[:message][:threadId], labelIds: ["INBOX"]}} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(draft_hash)) 64 | 65 | 66 | d.message.labelIds = ["INBOX"] 67 | new_d = d.save 68 | assert_equal ["INBOX"], new_d.message.labelIds 69 | assert_not_equal d.object_id, new_d.object_id 70 | new_d = d.save! 71 | assert_equal d.object_id, new_d.object_id 72 | end 73 | 74 | should "create should return a new Draft" do 75 | draft_hash = test_draft 76 | draft_hash.delete(:id) 77 | d = Gmail::Draft.new draft_hash 78 | # those two lines are required because raw generation change between two calls... 79 | raw = d.message.raw 80 | d.message.raw = raw 81 | ### 82 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.create, parameters: {userId: "me"}, body_object:{message: {raw: d.message.raw, threadId: draft_hash[:message][:threadId], labelIds: draft_hash[:message][:labelIds]}} , headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_draft)) 83 | created_d = d.save! 84 | assert_equal Gmail::Draft, created_d.class 85 | assert_equal test_draft[:id], d.id 86 | end 87 | 88 | 89 | should "Draft should be sendable and return a Message" do 90 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.to_h['gmail.users.drafts.send'], parameters: {userId: "me"}, body_object:{id: test_draft[:id]} , headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message)) 91 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.get, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message)) 92 | 93 | d = Gmail::Draft.new test_draft 94 | m = d.deliver 95 | 96 | assert m.kind_of?Gmail::Message 97 | 98 | end 99 | 100 | 101 | 102 | 103 | end 104 | end -------------------------------------------------------------------------------- /test/gmail/gmail_object_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | module Gmail 4 | class GmailObjectTest < Test::Unit::TestCase 5 | should "implement #respond_to correctly" do 6 | obj = Gmail::GmailObject.new({ :id => 1, :foo => 'bar' }) 7 | assert_not_nil obj.id 8 | assert_not_nil obj.foo 9 | assert_nil obj.other 10 | end 11 | 12 | should "detail and refresh a Gmail object correctly" do 13 | obj = Gmail::GmailObject.new test_message 14 | exception = assert_raise do obj.refresh end 15 | assert_equal "Can't refresh a generic GmailObject. It needs to be a Thread, Message, Draft or Label", exception.message 16 | exception = assert_raise do obj.detailed end 17 | assert_equal "Can't detail a generic GmailObject. It needs to be a Thread, Message, Draft or Label", exception.message 18 | 19 | not_generic_object = Gmail::Message.new test_message 20 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.get, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 21 | 22 | new_o = not_generic_object.detailed 23 | assert_not_equal new_o.object_id, not_generic_object.object_id 24 | 25 | new_o = not_generic_object.refresh 26 | assert_equal new_o.object_id, not_generic_object.object_id 27 | 28 | end 29 | 30 | should "recursively call to_hash on GmailObject" do 31 | nested = Gmail::GmailObject.new({ :id => 7, :foo => 'bar' }) 32 | obj = Gmail::GmailObject.new({ :id => 1}) 33 | obj.nested = nested 34 | expected_hash = { :id => 1, :nested => {:id => 7, :foo => 'bar'} } 35 | assert_equal expected_hash, obj.to_hash 36 | end 37 | 38 | end 39 | end -------------------------------------------------------------------------------- /test/gmail/label_test.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path('../../test_helper', __FILE__) 3 | 4 | module Gmail 5 | class LabelTest < Test::Unit::TestCase 6 | 7 | should "Labels should be listable" do 8 | @mock.expects(:execute).with(api_method: Gmail.service.users.labels.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_label_list)) 9 | list = Gmail::Label.all 10 | assert list.kind_of? Array 11 | assert list[0].kind_of? Gmail::Label 12 | end 13 | 14 | 15 | context "Retrieve a Label" do 16 | should "Label should be retrievable by id" do 17 | 18 | @mock.expects(:execute).with(api_method: Gmail.service.users.labels.get, parameters: {userId: "me", id: test_label[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_label)) 19 | l = Gmail::Label.get(test_label[:id]) 20 | assert l.kind_of?Gmail::Label 21 | assert_equal test_label[:id], l.id 22 | end 23 | 24 | 25 | [:inbox, :sent, :trash, :important, :starred, :draft, :spam, :unread, :category_updates, :category_promotions, :category_social, :category_personal, :category_forums ].each do |label_id| 26 | should "System Label should be retrievable by calling #{label_id.to_s}" do 27 | @mock.expects(:execute).with(api_method: Gmail.service.users.labels.get, parameters: {userId: "me", id: label_id.to_s.upcase}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_label_list[:labels].select{|l| l[:id] == label_id.to_s.upcase}.first)) 28 | l = Gmail::Label.send(label_id.to_s) 29 | assert l.kind_of?Gmail::Label 30 | assert_equal label_id.to_s.upcase, l.id 31 | end 32 | end 33 | 34 | end 35 | 36 | 37 | context "Access list of Messages from Label" do 38 | should "Access list of Messages" do 39 | label = Gmail::Label.new test_label 40 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", labelIds: [test_label[:id]]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 41 | list = label.messages 42 | assert list.kind_of? Array 43 | assert list[0].kind_of? Gmail::Message 44 | end 45 | 46 | should 'Access list of unread Messages' do 47 | label = Gmail::Label.new test_label 48 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", labelIds: [test_label[:id], "UNREAD"]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 49 | list = label.unread_messages 50 | assert list.kind_of? Array 51 | assert list[0].kind_of? Gmail::Message 52 | end 53 | 54 | should 'Access filtered Messages' do 55 | label = Gmail::Label.new test_label 56 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", labelIds: ["IMPORTANT", "COOL", test_label[:id]], threadId: "1"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 57 | list = label.messages(threadId: "1", labelIds: ["IMPORTANT", "COOL"]) 58 | assert list.kind_of? Array 59 | assert list[0].kind_of? Gmail::Message 60 | end 61 | 62 | should "Access list of Threads" do 63 | label = Gmail::Label.new test_label 64 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me", labelIds: [test_label[:id]]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread_list)) 65 | list = label.threads 66 | assert list.kind_of? Array 67 | assert list[0].kind_of? Gmail::Thread 68 | end 69 | 70 | should 'Access list of unread Threads' do 71 | label = Gmail::Label.new test_label 72 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me", labelIds: [test_label[:id], "UNREAD"]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread_list)) 73 | list = label.unread_threads 74 | assert list.kind_of? Array 75 | assert list[0].kind_of? Gmail::Thread 76 | end 77 | 78 | should 'Access filtered Threads' do 79 | label = Gmail::Label.new test_label 80 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me", labelIds: ["IMPORTANT", "COOL", test_label[:id]], threadId: "1"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread_list)) 81 | list = label.threads(threadId: "1", labelIds: ["IMPORTANT", "COOL"]) 82 | assert list.kind_of? Array 83 | assert list[0].kind_of? Gmail::Thread 84 | end 85 | 86 | 87 | 88 | end 89 | 90 | 91 | should "Label should be deletable" do 92 | @mock.expects(:execute).with(api_method: Gmail.service.users.labels.delete, parameters: {userId: "me", id: test_label[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response("")) 93 | d = Gmail::Label.new(test_label) 94 | r = d.delete 95 | assert r 96 | end 97 | 98 | should "Label should be updateable" do 99 | 100 | label = Gmail::Label.new test_label(:messageListVisibility=>"show") 101 | 102 | assert_equal "show", label.messageListVisibility 103 | 104 | 105 | @mock.expects(:execute).with(api_method: Gmail.service.users.labels.update, parameters: {id: test_label[:id], userId: "me"}, body_object:test_label(:messageListVisibility=>"hide") , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_label(:messageListVisibility=>"hide"))) 106 | 107 | label.messageListVisibility = "hide" 108 | new_l = label.save 109 | assert_equal "hide", new_l.messageListVisibility 110 | assert_not_equal label.object_id, new_l.object_id 111 | new_l = label.save! 112 | assert_equal label.object_id, new_l.object_id 113 | end 114 | 115 | should "create should return a new Label" do 116 | label_hash = test_label 117 | label_hash.delete(:id) 118 | label = Gmail::Label.new label_hash 119 | @mock.expects(:execute).with(api_method: Gmail.service.users.labels.create, parameters: {userId: "me"}, body_object:label_hash , headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_label)) 120 | created_l = label.save! 121 | assert_equal Gmail::Label, created_l.class 122 | assert_equal test_label[:id], label.id 123 | end 124 | 125 | 126 | 127 | 128 | end 129 | end -------------------------------------------------------------------------------- /test/gmail/message_test.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # encoding: utf-8 3 | require File.expand_path('../../test_helper', __FILE__) 4 | 5 | module Gmail 6 | class MessageTest < Test::Unit::TestCase 7 | 8 | should "messages should be listable" do 9 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 10 | list = Gmail::Message.all 11 | assert_equal Array, list.class 12 | assert_equal Gmail::Message, list[0].class 13 | end 14 | 15 | should "message should be retrievable by id" do 16 | 17 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.get, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message)) 18 | t = Gmail::Message.get(test_message[:id]) 19 | assert_equal Gmail::Message, t.class 20 | assert_equal test_message[:id], t.id 21 | end 22 | 23 | should "message construct should set some basics values" do 24 | 25 | m = Gmail::Message.new(test_message) 26 | ["From", "To", "Cc", "Subject", "Bcc", "Date", "Message-ID", "References", "In-Reply-To", "Delivered-To"].each do |method| 27 | assert_equal test_message[:payload][:headers].select{|h| h[:name].downcase == method.downcase}.first[:value], m.send(method.downcase.tr("-", "_")) 28 | end 29 | assert_not_nil m.text || m.body || m.html 30 | 31 | end 32 | 33 | should "message (with strange format) construct should set at least body, text or html" do 34 | 35 | m = Gmail::Message.new(test_strange_message) 36 | assert_not_nil m.text || m.body || m.html 37 | 38 | end 39 | 40 | 41 | 42 | 43 | should "Access thread from message" do 44 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.get, parameters: {userId: "me", id: test_message[:threadId]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread)) 45 | m = Gmail::Message.new(test_message) 46 | t = m.thread 47 | assert_equal test_message[:threadId], m.thread_id 48 | assert_equal Gmail::Thread, t.class 49 | end 50 | 51 | should "message should be deletable" do 52 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.delete, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response("")) 53 | t = Gmail::Message.new(test_message) 54 | r = t.delete 55 | assert r 56 | end 57 | 58 | should "message should be thrashable" do 59 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.trash, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message)) 60 | t = Gmail::Message.new(test_message) 61 | r = t.trash 62 | assert_equal Gmail::Message, r.class 63 | end 64 | 65 | should "message should be unthrashable" do 66 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.untrash, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message)) 67 | t = Gmail::Message.new(test_message) 68 | r = t.untrash 69 | assert_equal Gmail::Message, r.class 70 | end 71 | 72 | context "Modifying Labels" do 73 | should "message should be starrable" do 74 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: ["STARRED"], removeLabelIds: []} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 75 | t = Gmail::Message.new(test_message) 76 | r = t.star 77 | assert_equal Gmail::Message, r.class 78 | assert_not_equal t.object_id, r.object_id 79 | 80 | r = t.star! 81 | 82 | assert_equal t.object_id, r.object_id 83 | 84 | end 85 | 86 | should "message should be unstarrable" do 87 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: [], removeLabelIds: ["STARRED"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 88 | t = Gmail::Message.new(test_message) 89 | r = t.unstar 90 | assert_equal Gmail::Message, r.class 91 | 92 | assert_not_equal t.object_id, r.object_id 93 | 94 | r = t.unstar! 95 | 96 | assert_equal t.object_id, r.object_id 97 | end 98 | 99 | should "message should be archivable" do 100 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: [], removeLabelIds: ["INBOX"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 101 | t = Gmail::Message.new(test_message) 102 | r = t.archive 103 | assert_equal Gmail::Message, r.class 104 | assert_not_equal t.object_id, r.object_id 105 | 106 | r = t.archive! 107 | 108 | assert_equal t.object_id, r.object_id 109 | end 110 | 111 | should "message should be unarchivable" do 112 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: ["INBOX"], removeLabelIds: []} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 113 | t = Gmail::Message.new(test_message) 114 | r = t.unarchive 115 | assert_equal Gmail::Message, r.class 116 | assert_not_equal t.object_id, r.object_id 117 | 118 | r = t.unarchive! 119 | 120 | assert_equal t.object_id, r.object_id 121 | end 122 | 123 | should "message should be markable as read" do 124 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: [], removeLabelIds: ["UNREAD"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 125 | t = Gmail::Message.new(test_message) 126 | r = t.mark_as_read 127 | assert_equal Gmail::Message, r.class 128 | assert_not_equal t.object_id, r.object_id 129 | 130 | r = t.mark_as_read! 131 | 132 | assert_equal t.object_id, r.object_id 133 | end 134 | 135 | should "message should be markable as unread" do 136 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: ["UNREAD"], removeLabelIds: []} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 137 | t = Gmail::Message.new(test_message) 138 | r = t.mark_as_unread 139 | assert_equal Gmail::Message, r.class 140 | assert_not_equal t.object_id, r.object_id 141 | 142 | r = t.mark_as_unread! 143 | 144 | assert_equal t.object_id, r.object_id 145 | end 146 | 147 | 148 | should "message label should be modifiable as wish" do 149 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.modify, parameters: {userId: "me", id: test_message[:id]}, body_object: {addLabelIds: ["UNREAD", "SOME COOL LABEL"], removeLabelIds: ["INBOX", "SOME NOT COOL LABEL"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 150 | t = Gmail::Message.new(test_message) 151 | r = t.modify ["UNREAD", "SOME COOL LABEL"], ["INBOX", "SOME NOT COOL LABEL"] 152 | assert_equal Gmail::Message, r.class 153 | assert_not_equal t.object_id, r.object_id 154 | 155 | r = t.modify! ["UNREAD", "SOME COOL LABEL"], ["INBOX", "SOME NOT COOL LABEL"] 156 | 157 | assert_equal t.object_id, r.object_id 158 | end 159 | 160 | end 161 | 162 | 163 | should "Helpers should work" do 164 | m = Gmail::Message.new test_message 165 | assert_false m.sent? 166 | assert_false m.inbox? 167 | assert_false m.unread? 168 | m = Gmail::Message.new test_inbox_message 169 | assert_false m.sent? 170 | assert m.inbox? 171 | assert_false m.unread? 172 | m = Gmail::Message.new test_sent_message 173 | assert m.sent? 174 | assert_false m.inbox? 175 | assert_false m.unread? 176 | m = Gmail::Message.new test_unread_message 177 | assert_false m.sent? 178 | assert_false m.inbox? 179 | assert m.unread? 180 | end 181 | 182 | 183 | should "Message should be searcheable" do 184 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", q: "from:(me) to:(you) subject:(subject) in:inbox before:2014/12/1 after:2014/11/1 test -{real}", labelIds:["UNREAD"]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 185 | list = Gmail::Message.search(from:"me", to: "you", subject: "subject", in: "inbox", before: "2014/12/1", after: "2014/11/1", has_words: "test", has_not_words: "real", labelIds: ["UNREAD"]) 186 | assert_equal Array, list.class 187 | assert_equal Gmail::Message, list[0].class 188 | end 189 | 190 | should "Message should construct RAW string correctly" do 191 | m = Gmail::Message.new test_message 192 | raw = Mail.new(Base64.urlsafe_decode64(m.raw)) 193 | assert raw.from 194 | assert raw.to 195 | assert raw.cc 196 | assert_equal m.bcc, raw.header['Bcc'].value 197 | assert_equal m.subject, raw.subject 198 | assert_equal m.in_reply_to, "<#{raw.in_reply_to}>" 199 | assert_equal m.references.tr("<", "").tr(">", "").split(" "), raw.references 200 | assert raw.text_part.body.raw_source 201 | assert raw.html_part.body.raw_source 202 | end 203 | 204 | should "Draft can be created from Message" do 205 | m = Gmail::Message.new test_message 206 | # raw generation change between two calls because date won't be the same... 207 | m.raw = m.raw 208 | ### 209 | @mock.expects(:execute).with(api_method: Gmail.service.users.drafts.create, parameters: {userId: "me"}, body_object:{message: {raw: m.raw, threadId: test_message[:threadId], labelIds: test_message[:labelIds]}} , headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_draft)) 210 | d = m.create_draft 211 | assert_equal Gmail::Draft, d.class 212 | 213 | end 214 | 215 | should "Message should be sendable and return a Message" do 216 | 217 | m = Gmail::Message.new test_message 218 | m.raw = m.raw 219 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.to_h['gmail.users.messages.send'], parameters: {userId: "me"}, body_object:{raw: m.raw, labelIds: test_message[:labelIds], threadId: test_message[:threadId]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 220 | 221 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.get, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 222 | 223 | 224 | new_m = m.deliver 225 | assert_equal Gmail::Message, new_m.class 226 | assert_not_equal new_m.object_id, m.object_id 227 | 228 | new_m = m.deliver! 229 | assert_equal Gmail::Message, new_m.class 230 | assert_equal new_m.object_id, m.object_id 231 | end 232 | 233 | 234 | should "Reply to sender contruct should be easy" do 235 | m = Gmail::Message.new test_to_reply_message 236 | reply_message = Gmail::Message.new test_reply_message 237 | @mock.expects(:execute).never 238 | expected_msg = Gmail::Message.new test_replied_message 239 | new_m = m.reply_sender_with reply_message 240 | 241 | assert_equal expected_msg.to, new_m.to 242 | assert_nil new_m.cc 243 | assert_nil new_m.bcc 244 | assert_equal expected_msg.subject, new_m.subject 245 | assert_equal expected_msg.references, new_m.references 246 | assert_equal expected_msg.in_reply_to, new_m.in_reply_to 247 | assert_equal expected_msg.thread_id, new_m.thread_id 248 | assert_equal expected_msg.body, new_m.body 249 | assert_nil new_m.html 250 | assert_nil new_m.text 251 | 252 | new_m = m.reply_sender_with(Gmail::Message.new test_reply_message_with_html) 253 | expected_msg = Gmail::Message.new(test_replied_message_with_html) 254 | 255 | assert_equal expected_msg.text, new_m.text 256 | assert_equal expected_msg.html, new_m.html 257 | assert_nil new_m.body 258 | 259 | end 260 | 261 | should "Reply to all construct should be easy" do 262 | m = Gmail::Message.new test_to_reply_message 263 | reply_message = Gmail::Message.new test_reply_message 264 | @mock.expects(:execute).never 265 | new_m = m.reply_all_with reply_message 266 | expected_msg = Gmail::Message.new test_replied_message 267 | 268 | assert_equal expected_msg.to, new_m.to 269 | assert_equal expected_msg.cc, new_m.cc 270 | assert_nil new_m.bcc 271 | assert_equal expected_msg.subject, new_m.subject 272 | assert_equal expected_msg.references, new_m.references 273 | assert_equal expected_msg.in_reply_to, new_m.in_reply_to 274 | assert_equal expected_msg.thread_id, new_m.thread_id 275 | assert_equal expected_msg.body, new_m.body 276 | assert_nil new_m.html 277 | assert_nil new_m.text 278 | 279 | new_m = m.reply_all_with(Gmail::Message.new test_reply_message_with_html) 280 | expected_msg = Gmail::Message.new(test_replied_message_with_html) 281 | 282 | assert_equal expected_msg.text, new_m.text 283 | assert_equal expected_msg.html, new_m.html 284 | assert_nil new_m.body 285 | 286 | 287 | end 288 | 289 | should "Construct correctly set_headers_for_reply" do 290 | m = Gmail::Message.new({body: ""}) 291 | m.from = "\"John, Malkovich\" john@malkovich.com" 292 | m.to = "\"Julie, Desk\"julie@juliedesk.com, \"Judith, Desk\"judith@juliedesk.com" 293 | m.delivered_to = "julie@juliedesk.com" 294 | new_msg = m.send(:set_headers_for_reply, Gmail::Message.new({body: ""})) 295 | 296 | #assert_equal new_msg.from, "\"Julie, Desk\"julie@juliedesk.com" 297 | assert_equal new_msg.to, "\"John, Malkovich\" john@malkovich.com" 298 | assert_equal new_msg.cc, "\"Judith, Desk\"judith@juliedesk.com" 299 | end 300 | 301 | should "Reply to all construct should be easy and call getProfile if delivered_to is not set" do 302 | m = Gmail::Message.new test_to_reply_message2 303 | reply_message = Gmail::Message.new test_reply_message 304 | @mock.expects(:execute).with(api_method: Gmail.service.users.to_h['gmail.users.getProfile'],parameters: {userId: "me"} , headers: {'Content-Type' => 'application/json'}).once.returns(test_response({emailAddress: "julie@juliedesk.com"})) 305 | new_m = m.reply_all_with reply_message 306 | expected_msg = Gmail::Message.new test_replied_message 307 | 308 | assert_equal expected_msg.to, new_m.to 309 | assert_equal expected_msg.cc, new_m.cc 310 | assert_nil new_m.bcc 311 | assert_equal expected_msg.subject, new_m.subject 312 | assert_equal expected_msg.references, new_m.references 313 | assert_equal expected_msg.in_reply_to, new_m.in_reply_to 314 | assert_equal expected_msg.thread_id, new_m.thread_id 315 | assert_equal expected_msg.body, new_m.body 316 | assert_nil new_m.html 317 | assert_nil new_m.text 318 | 319 | 320 | end 321 | 322 | should "forward construct should be easy" do 323 | m = Gmail::Message.new test_to_reply_message 324 | forward_message = Gmail::Message.new(test_forward_message) 325 | @mock.expects(:execute).never 326 | new_m = m.forward_with forward_message 327 | expected_msg = Gmail::Message.new test_forwarded_message 328 | # to be completed to be fully tested 329 | 330 | assert_equal expected_msg.to, new_m.to 331 | assert_equal expected_msg.cc, new_m.cc 332 | assert_nil new_m.bcc 333 | assert_equal expected_msg.subject, new_m.subject 334 | assert_equal expected_msg.references, new_m.references 335 | assert_equal expected_msg.in_reply_to, new_m.in_reply_to 336 | assert_equal expected_msg.thread_id, new_m.thread_id 337 | assert_equal expected_msg.body, new_m.body 338 | assert_nil new_m.html 339 | assert_nil new_m.text 340 | 341 | forward_message = Gmail::Message.new({to: "test@test.com", bbc: "coucou", cc: "test@couocu.com, second@second.com", subject: "cool subject", html: "test", text: "test"}) 342 | new_m = m.forward_with forward_message 343 | expected_msg = Gmail::Message.new test_forwarded_message_with_html 344 | 345 | assert_equal expected_msg.text, new_m.text 346 | assert_equal expected_msg.html, new_m.html 347 | assert_nil new_m.body 348 | end 349 | 350 | should "Insert call should be easy" do 351 | m = Gmail::Message.new test_message 352 | m.raw = m.raw 353 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.insert, parameters: {userId: "me"}, body_object:{raw: m.raw, labelIds: test_message[:labelIds], threadId: test_message[:threadId]} , headers: {'Content-Type' => 'application/json'} ).twice.returns(test_response(test_message)) 354 | @mock.expects(:execute).with(api_method: Gmail.service.users.messages.get, parameters: {userId: "me", id: test_message[:id]}, headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_message)) 355 | 356 | 357 | new_m = m.insert 358 | 359 | assert_not_equal m.object_id, new_m.object_id 360 | new_m = m.insert! 361 | assert_equal m.object_id, new_m.object_id 362 | end 363 | 364 | end 365 | end -------------------------------------------------------------------------------- /test/gmail/thread_test.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path('../../test_helper', __FILE__) 3 | 4 | module Gmail 5 | class ThreadTest < Test::Unit::TestCase 6 | 7 | should "Threads should be listable" do 8 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread_list)) 9 | list = Gmail::Thread.all 10 | assert_equal Array, list.class 11 | assert_equal Gmail::Thread, list[0].class 12 | end 13 | 14 | should "Thread should be retrievable by id" do 15 | 16 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.get, parameters: {userId: "me", id: test_thread[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread)) 17 | t = Gmail::Thread.get(test_thread[:id]) 18 | assert_equal Gmail::Thread, t.class 19 | assert_equal test_thread[:id], t.id 20 | end 21 | 22 | 23 | context "Access list of Messages from thread" do 24 | should "Access list of Messages" do 25 | thread = Gmail::Thread.new test_thread 26 | # @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", threadId: [test_thread[:id]]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 27 | list = thread.messages 28 | assert_equal Array, list.class 29 | assert_equal Gmail::Message, list[0].class 30 | end 31 | 32 | should "Access list of Messages after selecting from list" do 33 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me"}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread_list)) 34 | thread_list = Gmail::Thread.all 35 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.get, parameters: {userId: "me", id: test_thread[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 36 | thread = thread_list.first 37 | list = thread.messages 38 | assert_equal Array, list.class 39 | assert_equal Gmail::Message, list[0].class 40 | end 41 | 42 | 43 | should 'Access list of unread Messages' do 44 | thread = Gmail::Thread.new test_thread 45 | #@mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", threadId: [test_thread[:id]], labelIds: ["UNREAD"]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 46 | list = thread.unread_messages 47 | assert_equal Array, list.class 48 | assert_equal Gmail::Message, list[0].class 49 | end 50 | 51 | should 'Access list of sent Messages' do 52 | thread = Gmail::Thread.new test_thread 53 | # @mock.expects(:execute).with(api_method: Gmail.service.users.messages.list, parameters: {userId: "me", threadId: [test_thread[:id]], labelIds: ["SENT"]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_message_list)) 54 | list = thread.unread_messages 55 | assert_equal Array, list.class 56 | assert_equal Gmail::Message, list[0].class 57 | end 58 | end 59 | 60 | 61 | should "Thread should be deletable" do 62 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.delete, parameters: {userId: "me", id: test_thread[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response("")) 63 | t = Gmail::Thread.new(test_thread) 64 | r = t.delete 65 | assert r 66 | end 67 | 68 | should "Thread should be thrashable" do 69 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.trash, parameters: {userId: "me", id: test_thread[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread)) 70 | t = Gmail::Thread.new(test_thread) 71 | r = t.trash 72 | assert_equal Gmail::Thread, r.class 73 | end 74 | 75 | should "Thread should be unthrashable" do 76 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.untrash, parameters: {userId: "me", id: test_thread[:id]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread)) 77 | t = Gmail::Thread.new(test_thread) 78 | r = t.untrash 79 | assert_equal Gmail::Thread, r.class 80 | end 81 | 82 | context "Modifying Labels" do 83 | should "Thread should be starrable" do 84 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: ["STARRED"], removeLabelIds: []} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 85 | t = Gmail::Thread.new(test_thread) 86 | r = t.star 87 | assert_equal Gmail::Thread, r.class 88 | assert_not_equal t.object_id, r.object_id 89 | 90 | r = t.star! 91 | 92 | assert_equal t.object_id, r.object_id 93 | 94 | end 95 | 96 | should "Thread should be unstarrable" do 97 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: [], removeLabelIds: ["STARRED"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 98 | t = Gmail::Thread.new(test_thread) 99 | r = t.unstar 100 | assert_equal Gmail::Thread, r.class 101 | 102 | assert_not_equal t.object_id, r.object_id 103 | 104 | r = t.unstar! 105 | 106 | assert_equal t.object_id, r.object_id 107 | end 108 | 109 | should "Thread should be archivable" do 110 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: [], removeLabelIds: ["INBOX"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 111 | t = Gmail::Thread.new(test_thread) 112 | r = t.archive 113 | assert_equal Gmail::Thread, r.class 114 | assert_not_equal t.object_id, r.object_id 115 | 116 | r = t.archive! 117 | 118 | assert_equal t.object_id, r.object_id 119 | end 120 | 121 | should "Thread should be unarchivable" do 122 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: ["INBOX"], removeLabelIds: []} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 123 | t = Gmail::Thread.new(test_thread) 124 | r = t.unarchive 125 | assert_equal Gmail::Thread, r.class 126 | assert_not_equal t.object_id, r.object_id 127 | 128 | r = t.unarchive! 129 | 130 | assert_equal t.object_id, r.object_id 131 | end 132 | 133 | should "Thread should be markable as read" do 134 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: [], removeLabelIds: ["UNREAD"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 135 | t = Gmail::Thread.new(test_thread) 136 | r = t.mark_as_read 137 | assert_equal Gmail::Thread, r.class 138 | assert_not_equal t.object_id, r.object_id 139 | 140 | r = t.mark_as_read! 141 | 142 | assert_equal t.object_id, r.object_id 143 | end 144 | 145 | should "Thread should be markable as unread" do 146 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: ["UNREAD"], removeLabelIds: []} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 147 | t = Gmail::Thread.new(test_thread) 148 | r = t.mark_as_unread 149 | assert_equal Gmail::Thread, r.class 150 | assert_not_equal t.object_id, r.object_id 151 | 152 | r = t.mark_as_unread! 153 | 154 | assert_equal t.object_id, r.object_id 155 | end 156 | 157 | 158 | should "Thread label should be modifiable as wish" do 159 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.modify, parameters: {userId: "me", id: test_thread[:id]}, body_object: {addLabelIds: ["UNREAD", "SOME COOL LABEL"], removeLabelIds: ["INBOX", "SOME NOT COOL LABEL"]} , headers: {'Content-Type' => 'application/json'}).twice.returns(test_response(test_thread)) 160 | t = Gmail::Thread.new(test_thread) 161 | r = t.modify ["UNREAD", "SOME COOL LABEL"], ["INBOX", "SOME NOT COOL LABEL"] 162 | assert_equal Gmail::Thread, r.class 163 | assert_not_equal t.object_id, r.object_id 164 | 165 | r = t.modify! ["UNREAD", "SOME COOL LABEL"], ["INBOX", "SOME NOT COOL LABEL"] 166 | 167 | assert_equal t.object_id, r.object_id 168 | end 169 | 170 | end 171 | 172 | 173 | should "Thread should be searcheable" do 174 | @mock.expects(:execute).with(api_method: Gmail.service.users.threads.list, parameters: {userId: "me", q: "from:(me) to:(you) subject:(subject) in:inbox before:2014/12/1 after:2014/11/1 test -{real}", labelIds:["UNREAD"]}, headers: {'Content-Type' => 'application/json'}).once.returns(test_response(test_thread_list)) 175 | list = Gmail::Thread.search(from:"me", to: "you", subject: "subject", in: "inbox", before: "2014/12/1", after: "2014/11/1", has_words: "test", has_not_words: "real", labelIds: ["UNREAD"]) 176 | assert_equal Array, list.class 177 | assert_equal Gmail::Thread, list[0].class 178 | end 179 | 180 | 181 | 182 | 183 | end 184 | end -------------------------------------------------------------------------------- /test/gmail/util_test.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path('../../test_helper', __FILE__) 3 | 4 | module Gmail 5 | class UtilTest < Test::Unit::TestCase 6 | 7 | should "Symbolize Name should work properly" do 8 | assert_equal(({:test1=> "test"}), Gmail::Util.symbolize_names({"test1"=>'test'})) 9 | m =Gmail::Message.new test_message 10 | assert_equal m, Gmail::Util.symbolize_names(m) 11 | assert_equal(({test: {nested:[{coucou: "1", coucou2: "2"}]}}), Gmail::Util.symbolize_names({"test"=>{"nested"=>[{"coucou"=>"1","coucou2"=>"2"}]}})) 12 | assert_equal [{coucou: "1", coucou2: "2"}], Gmail::Util.symbolize_names([{"coucou"=>"1", "coucou2"=>"2"}]) 13 | end 14 | 15 | #testing of convert_to_gmail_object is not necessary as it is tested through all other test files 16 | 17 | end 18 | end -------------------------------------------------------------------------------- /test/test_data.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | module Gmail 3 | module TestData 4 | def test_response(body, code=200) 5 | # When an exception is raised, restclient clobbers method_missing. Hence we 6 | # can't just use the stubs interface. 7 | body = JSON.generate(body) if !(body.kind_of? String) 8 | m = mock 9 | m.instance_variable_set('@gmail_values', { :body => body, :code => code }) 10 | def m.body; @gmail_values[:body]; end 11 | def m.code; @gmail_values[:code]; end 12 | m 13 | end 14 | 15 | def test_draft(params={}) 16 | {:id=>"1490204870554280932", :message=>{:id=>"14ae456f2ff1e3e4", :threadId=>"14ae456f2ff1e3e4", :labelIds=>["DRAFT"], :snippet=>"sd Cordialement, Julie", :historyId=>"227960", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"MIME-Version", :value=>"1.0"}, {:name=>"Received", :value=>"by 10.64.21.4 with HTTP; Tue, 13 Jan 2015 09:28:31 -0800 (PST)"}, {:name=>"Date", :value=>"Tue, 13 Jan 2015 18:28:31 +0100"}, {:name=>"Message-ID", :value=>""}, {:name=>"Subject", :value=>""}, {:name=>"From", :value=>"Julie Desk "}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=f46d042abf34a36bd9050c8bf335"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}], :body=>{:size=>28, :data=>"c2QNCkNvcmRpYWxlbWVudCwNCg0KSnVsaWUNCg=="}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}], :body=>{:size=>154, :data=>"PGRpdiBkaXI9Imx0ciI-c2Q8YnIgY2xlYXI9ImFsbCI-PGRpdj48ZGl2IGNsYXNzPSJnbWFpbF9zaWduYXR1cmUiPjxkaXYgZGlyPSJsdHIiPkNvcmRpYWxlbWVudCw8ZGl2Pjxicj48L2Rpdj48ZGl2Pkp1bGllPC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-DQo8L2Rpdj4NCg=="}}]}, :sizeEstimate=>710}} .merge(params) 17 | 18 | end 19 | 20 | 21 | def test_strange_message #it has payload.parts.parts 22 | {:id=>"14ae95209c15af0a", :threadId=>"14ae94601bae5138", :labelIds=>["IMPORTANT", "CATEGORY_PERSONAL"], :snippet=>"Bonjour Julie, Le plus tôt sera le mieux, j'ai hâte ! Je bloque le 12/02 Merci De : Julie Desk [", :historyId=>"231473", :payload=>{:mimeType=>"multipart/related", :filename=>"", :headers=>[{:name=>"Delivered-To", :value=>"julie@juliedesk.com"}, {:name=>"Received", :value=>"by 10.64.89.39 with SMTP id bl7csp1683249ieb; Wed, 14 Jan 2015 08:41:16 -0800 (PST)"}, {:name=>"X-Received", :value=>"by 10.194.76.205 with SMTP id m13mr8833652wjw.39.1421253675567; Wed, 14 Jan 2015 08:41:15 -0800 (PST)"}, {:name=>"Return-Path", :value=>""}, {:name=>"Received", :value=>"from emea01-am1-obe.outbound.protection.outlook.com (mail-am1on0770.outbound.protection.outlook.com. [2a01:111:f400:fe00::770]) by mx.google.com with ESMTPS id v10si5545148wix.4.2015.01.14.08.41.14 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 14 Jan 2015 08:41:15 -0800 (PST)"}, {:name=>"Received-SPF", :value=>"pass (google.com: domain of sandra.perez@esnumerique.com designates 2a01:111:f400:fe00::770 as permitted sender) client-ip=2a01:111:f400:fe00::770;"}, {:name=>"Authentication-Results", :value=>"mx.google.com; spf=pass (google.com: domain of sandra.perez@esnumerique.com designates 2a01:111:f400:fe00::770 as permitted sender) smtp.mail=sandra.perez@esnumerique.com"}, {:name=>"Received", :value=>"from AMSPR06MB102.eurprd06.prod.outlook.com (10.242.90.147) by DB3PR06MB0937.eurprd06.prod.outlook.com (25.161.60.149) with Microsoft SMTP Server (TLS) id 15.1.53.17; Wed, 14 Jan 2015 16:41:13 +0000"}, {:name=>"Received", :value=>"from AMSPR06MB102.eurprd06.prod.outlook.com ([169.254.6.131]) by AMSPR06MB102.eurprd06.prod.outlook.com ([169.254.6.131]) with mapi id 15.01.0053.000; Wed, 14 Jan 2015 16:41:13 +0000"}, {:name=>"From", :value=>"Sandra Perez "}, {:name=>"To", :value=>"Julie Desk , Vincent Klingbeil "}, {:name=>"Subject", :value=>"RE: Dej"}, {:name=>"Thread-Topic", :value=>"Dej"}, {:name=>"Thread-Index", :value=>"AdAwFmdsGWjLpOTkSh6OGW1svRYHwwAAi/qAAAAdX/A="}, {:name=>"Date", :value=>"Wed, 14 Jan 2015 16:41:13 +0000"}, {:name=>"Message-ID", :value=>""}, {:name=>"References", :value=>" "}, {:name=>"In-Reply-To", :value=>""}, {:name=>"Accept-Language", :value=>"fr-FR, en-US"}, {:name=>"Content-Language", :value=>"fr-FR"}, {:name=>"X-MS-Has-Attach", :value=>"yes"}, {:name=>"X-MS-TNEF-Correlator", :value=>""}, {:name=>"x-originating-ip", :value=>"[88.162.203.104]"}, {:name=>"authentication-results", :value=>"spf=none (sender IP is ) smtp.mailfrom=sandra.perez@esnumerique.com;"}, {:name=>"x-dmarcaction-test", :value=>"None"}, {:name=>"x-microsoft-antispam", :value=>"BCL:0;PCL:0;RULEID:(3005003);SRVR:DB3PR06MB0937;"}, {:name=>"x-exchange-antispam-report-test", :value=>"UriScan:;"}, {:name=>"x-exchange-antispam-report-cfa-test", :value=>"BCL:0;PCL:0;RULEID:;SRVR:DB3PR06MB0937;"}, {:name=>"x-forefront-prvs", :value=>"04569283F9"}, {:name=>"x-forefront-antispam-report", :value=>"SFV:NSPM;SFS:(10019020)(199003)(377424004)(189002)(68736005)(54356999)(76176999)(18206015028)(40100003)(99936001)(54606007)(102836002)(15975445007)(106356001)(50986999)(77156002)(62966003)(19625215002)(16236675004)(92566002)(33656002)(2950100001)(2900100001)(105586002)(221733001)(97736003)(46102003)(2656002)(87936001)(16601075003)(19300405004)(17760045003)(54206007)(66066001)(76576001)(19580395003)(101416001)(86362001)(74316001)(19580405001)(122556002)(19617315012)(64706001)(107886001)(19627595001);DIR:OUT;SFP:1102;SCL:1;SRVR:DB3PR06MB0937;H:AMSPR06MB102.eurprd06.prod.outlook.com;FPR:;SPF:None;MLV:sfv;PTR:InfoNoRecords;A:1;MX:1;LANG:fr;"}, {:name=>"received-spf", :value=>"None (protection.outlook.com: esnumerique.com does not designate permitted sender hosts)"}, {:name=>"Content-Type", :value=>"multipart/related; boundary=\"_005_AMSPR06MB102CF5CA6B835FEA9E50E6D97410AMSPR06MB102eurprd_\"; type=\"multipart/alternative\""}, {:name=>"MIME-Version", :value=>"1.0"}, {:name=>"X-OriginatorOrg", :value=>"esnumerique.com"}, {:name=>"X-MS-Exchange-CrossTenant-originalarrivaltime", :value=>"14 Jan 2015 16:41:13.6540 (UTC)"}, {:name=>"X-MS-Exchange-CrossTenant-fromentityheader", :value=>"Hosted"}, {:name=>"X-MS-Exchange-CrossTenant-id", :value=>"8d916988-dc6a-4604-9152-c47698104d7b"}, {:name=>"X-MS-Exchange-Transport-CrossTenantHeadersStamped", :value=>"DB3PR06MB0937"}], :body=>{:size=>0}, :parts=>[{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"multipart/alternative; boundary=\"_000_AMSPR06MB102CF5CA6B835FEA9E50E6D97410AMSPR06MB102eurprd_\""}], :body=>{:size=>0}, :parts=>[{:partId=>"0.0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=\"utf-8\""}, {:name=>"Content-Transfer-Encoding", :value=>"base64"}], :body=>{:size=>1223, :data=>"Qm9uam91ciBKdWxpZSwNCg0KTGUgcGx1cyB0w7R0IHNlcmEgbGUgbWlldXgsIGrigJlhaSBow6J0ZSAhDQoNCkplIGJsb3F1ZSBsZSAxMi8wMg0KDQpNZXJjaQ0KDQpEZSA6IEp1bGllIERlc2sgW21haWx0bzpqdWxpZUBqdWxpZWRlc2suY29tXQ0KRW52b3nDqSA6IG1lcmNyZWRpIDE0IGphbnZpZXIgMjAxNSAxNzozOQ0Kw4AgOiBWaW5jZW50IEtsaW5nYmVpbA0KQ2MgOiBTYW5kcmEgUGVyZXoNCk9iamV0IDogUmU6IERlag0KDQpCb25qb3VyIFNhbmRyYSwNCg0KVmluY2VudCBlc3QgZGlzcG9uaWJsZSBwb3VyIGTDqWpldW5lciBhdmVjIHZvdXMgYXV4IGhvcmFpcmVzIHN1aXZhbnRzIDoNCg0KLSBKZXVkaSAxMiBGw6l2cmllciDDoCAxMjozMA0KLSBWZW5kcmVkaSAxMyBGw6l2cmllciDDoCAxMjozMA0KLSBMdW5kaSAxNiBGw6l2cmllciDDoCAxMjozMA0KLSBNZXJjcmVkaSAxOCBGw6l2cmllciDDoCAxMjozMA0KDQoNClF1ZWwgaG9yYWlyZSB2b3VzIGNvbnZpZW50IGxlIG1pZXV4ID8NCltodHRwczovL21haWxmb29nYWUuYXBwc3BvdC5jb20vdD9zZW5kZXI9YWFuVnNhV1ZBYW5Wc2FXVmtaWE5yTG1OdmJRJTNEJTNEJnR5cGU9emVyb2NvbnRlbnQmZ3VpZD02NDcxZGFjYS0wODYwLTQ3YWEtYTlkZS1iYjI5MzFlOTUxMWZd4ZCnDQoNCkNvcmRpYWxlbWVudCwNCg0KSnVsaWUNCg0KMjAxNS0wMS0xNCAxNzoyOCBHTVQrMDE6MDAgVmluY2VudCBLbGluZ2JlaWwgPHZpbmNlbnRrbGluZ2JlaWxAYW1ldGl4LmNvbTxtYWlsdG86dmluY2VudGtsaW5nYmVpbEBhbWV0aXguY29tPj46DQpIZWxsbyBTYW5kcmEsDQoNCkp1bGllIChlbiBjYykgdmEgdGUgcHJvcG9zZXIgZGVzIGRpc3BvcyBwb3VyIHVuIGRlai4NCg0KQSB0csOocyBiaWVudMO0dCwNCg0KDQoNCi0tLQ0KDQpWaW5jZW50IEtMSU5HQkVJTCAgW2NpZDppbWFnZTAwMy5qcGdAMDFDRjFEQjIuQkZCMTVEOTBdICBEaXJlY3RldXIgQXNzb2Npw6kNCjExLCBydWUgUm91Z2Vtb250IC0gNzUwMDkgUGFyaXMNClRlbCAgIDogMDEgODQgMTYgMTYgNTUNCk1vYiA6IDA2IDIyIDcyIDgzIDE3DQpFLW1haWwgOiB2aW5jZW50LmtsaW5nYmVpbEBhbWV0aXguY29tPG1haWx0bzp2aW5jZW50LmtsaW5nYmVpbEBhbWV0aXguY29tPg0KU2l0ZSA6IGh0dHA6Ly93d3cuYW1ldGl4LmNvbTxodHRwOi8vd3d3LmFtZXRpeC5jb20vPg0KDQpbY2lkOmltYWdlMDA0LnBuZ0AwMUNGMURCMi5CRkIxNUQ5MF0NCg0KDQo="}}, {:partId=>"0.1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=\"utf-8\""}, {:name=>"Content-Transfer-Encoding", :value=>"base64"}], :body=>{:size=>8246, :data=>"PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6dz0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6bT0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2UvMjAwNC8xMi9vbW1sIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvVFIvUkVDLWh0bWw0MCI-DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04Ij4NCjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29udGVudD0iTWljcm9zb2Z0IFdvcmQgMTUgKGZpbHRlcmVkIG1lZGl1bSkiPg0KPCEtLVtpZiAhbXNvXT48c3R5bGU-dlw6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kb1w6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0Kd1w6KiB7YmVoYXZpb3I6dXJsKCNkZWZhdWx0I1ZNTCk7fQ0KLnNoYXBlIHtiZWhhdmlvcjp1cmwoI2RlZmF1bHQjVk1MKTt9DQo8L3N0eWxlPjwhW2VuZGlmXS0tPjxzdHlsZT48IS0tDQovKiBGb250IERlZmluaXRpb25zICovDQpAZm9udC1mYWNlDQoJe2ZvbnQtZmFtaWx5OiJDYW1icmlhIE1hdGgiOw0KCXBhbm9zZS0xOjIgNCA1IDMgNSA0IDYgMyAyIDQ7fQ0KQGZvbnQtZmFjZQ0KCXtmb250LWZhbWlseTpDYWxpYnJpOw0KCXBhbm9zZS0xOjIgMTUgNSAyIDIgMiA0IDMgMiA0O30NCkBmb250LWZhY2UNCgl7Zm9udC1mYW1pbHk6R2FkdWdpOw0KCXBhbm9zZS0xOjIgMTEgNSAyIDQgMiA0IDIgMiAzO30NCi8qIFN0eWxlIERlZmluaXRpb25zICovDQpwLk1zb05vcm1hbCwgbGkuTXNvTm9ybWFsLCBkaXYuTXNvTm9ybWFsDQoJe21hcmdpbjowY207DQoJbWFyZ2luLWJvdHRvbTouMDAwMXB0Ow0KCWZvbnQtc2l6ZToxMi4wcHQ7DQoJZm9udC1mYW1pbHk6IlRpbWVzIE5ldyBSb21hbiIsc2VyaWY7fQ0KYTpsaW5rLCBzcGFuLk1zb0h5cGVybGluaw0KCXttc28tc3R5bGUtcHJpb3JpdHk6OTk7DQoJY29sb3I6Ymx1ZTsNCgl0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lO30NCmE6dmlzaXRlZCwgc3Bhbi5Nc29IeXBlcmxpbmtGb2xsb3dlZA0KCXttc28tc3R5bGUtcHJpb3JpdHk6OTk7DQoJY29sb3I6cHVycGxlOw0KCXRleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmU7fQ0Kc3Bhbi5FbWFpbFN0eWxlMTcNCgl7bXNvLXN0eWxlLXR5cGU6cGVyc29uYWwtcmVwbHk7DQoJZm9udC1mYW1pbHk6IkNhbGlicmkiLHNhbnMtc2VyaWY7DQoJY29sb3I6IzFGNDk3RDt9DQouTXNvQ2hwRGVmYXVsdA0KCXttc28tc3R5bGUtdHlwZTpleHBvcnQtb25seTsNCglmb250LWZhbWlseToiQ2FsaWJyaSIsc2Fucy1zZXJpZjsNCgltc28tZmFyZWFzdC1sYW5ndWFnZTpFTi1VUzt9DQpAcGFnZSBXb3JkU2VjdGlvbjENCgl7c2l6ZTo2MTIuMHB0IDc5Mi4wcHQ7DQoJbWFyZ2luOjcwLjg1cHQgNzAuODVwdCA3MC44NXB0IDcwLjg1cHQ7fQ0KZGl2LldvcmRTZWN0aW9uMQ0KCXtwYWdlOldvcmRTZWN0aW9uMTt9DQotLT48L3N0eWxlPjwhLS1baWYgZ3RlIG1zbyA5XT48eG1sPg0KPG86c2hhcGVkZWZhdWx0cyB2OmV4dD0iZWRpdCIgc3BpZG1heD0iMTAyNiIgLz4NCjwveG1sPjwhW2VuZGlmXS0tPjwhLS1baWYgZ3RlIG1zbyA5XT48eG1sPg0KPG86c2hhcGVsYXlvdXQgdjpleHQ9ImVkaXQiPg0KPG86aWRtYXAgdjpleHQ9ImVkaXQiIGRhdGE9IjEiIC8-DQo8L286c2hhcGVsYXlvdXQ-PC94bWw-PCFbZW5kaWZdLS0-DQo8L2hlYWQ-DQo8Ym9keSBsYW5nPSJGUiIgbGluaz0iYmx1ZSIgdmxpbms9InB1cnBsZSI-DQo8ZGl2IGNsYXNzPSJXb3JkU2VjdGlvbjEiPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OyxzYW5zLXNlcmlmO2NvbG9yOiMxRjQ5N0Q7bXNvLWZhcmVhc3QtbGFuZ3VhZ2U6RU4tVVMiPkJvbmpvdXIgSnVsaWUsDQo8bzpwPjwvbzpwPjwvc3Bhbj48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExLjBwdDtmb250LWZhbWlseTomcXVvdDtDYWxpYnJpJnF1b3Q7LHNhbnMtc2VyaWY7Y29sb3I6IzFGNDk3RDttc28tZmFyZWFzdC1sYW5ndWFnZTpFTi1VUyI-PG86cD4mbmJzcDs8L286cD48L3NwYW4-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OyxzYW5zLXNlcmlmO2NvbG9yOiMxRjQ5N0Q7bXNvLWZhcmVhc3QtbGFuZ3VhZ2U6RU4tVVMiPkxlIHBsdXMgdMO0dCBzZXJhIGxlIG1pZXV4LCBq4oCZYWkgaMOidGUmbmJzcDshDQo8bzpwPjwvbzpwPjwvc3Bhbj48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExLjBwdDtmb250LWZhbWlseTomcXVvdDtDYWxpYnJpJnF1b3Q7LHNhbnMtc2VyaWY7Y29sb3I6IzFGNDk3RDttc28tZmFyZWFzdC1sYW5ndWFnZTpFTi1VUyI-PG86cD4mbmJzcDs8L286cD48L3NwYW4-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OyxzYW5zLXNlcmlmO2NvbG9yOiMxRjQ5N0Q7bXNvLWZhcmVhc3QtbGFuZ3VhZ2U6RU4tVVMiPkplIGJsb3F1ZSBsZSAxMi8wMjxvOnA-PC9vOnA-PC9zcGFuPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTEuMHB0O2ZvbnQtZmFtaWx5OiZxdW90O0NhbGlicmkmcXVvdDssc2Fucy1zZXJpZjtjb2xvcjojMUY0OTdEO21zby1mYXJlYXN0LWxhbmd1YWdlOkVOLVVTIj48bzpwPiZuYnNwOzwvbzpwPjwvc3Bhbj48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExLjBwdDtmb250LWZhbWlseTomcXVvdDtDYWxpYnJpJnF1b3Q7LHNhbnMtc2VyaWY7Y29sb3I6IzFGNDk3RDttc28tZmFyZWFzdC1sYW5ndWFnZTpFTi1VUyI-TWVyY2k8bzpwPjwvbzpwPjwvc3Bhbj48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExLjBwdDtmb250LWZhbWlseTomcXVvdDtDYWxpYnJpJnF1b3Q7LHNhbnMtc2VyaWY7Y29sb3I6IzFGNDk3RDttc28tZmFyZWFzdC1sYW5ndWFnZTpFTi1VUyI-PG86cD4mbmJzcDs8L286cD48L3NwYW4-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI-PGI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMS4wcHQ7Zm9udC1mYW1pbHk6JnF1b3Q7Q2FsaWJyaSZxdW90OyxzYW5zLXNlcmlmIj5EZSZuYnNwOzo8L3NwYW4-PC9iPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTEuMHB0O2ZvbnQtZmFtaWx5OiZxdW90O0NhbGlicmkmcXVvdDssc2Fucy1zZXJpZiI-IEp1bGllIERlc2sgW21haWx0bzpqdWxpZUBqdWxpZWRlc2suY29tXQ0KPGJyPg0KPGI-RW52b3nDqSZuYnNwOzo8L2I-IG1lcmNyZWRpIDE0IGphbnZpZXIgMjAxNSAxNzozOTxicj4NCjxiPsOAJm5ic3A7OjwvYj4gVmluY2VudCBLbGluZ2JlaWw8YnI-DQo8Yj5DYyZuYnNwOzo8L2I-IFNhbmRyYSBQZXJlejxicj4NCjxiPk9iamV0Jm5ic3A7OjwvYj4gUmU6IERlajxvOnA-PC9vOnA-PC9zcGFuPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxvOnA-Jm5ic3A7PC9vOnA-PC9wPg0KPGRpdj4NCjxkaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj5Cb25qb3VyIFNhbmRyYSw8YnI-DQo8YnI-DQpWaW5jZW50IGVzdCBkaXNwb25pYmxlIHBvdXIgZMOpamV1bmVyIGF2ZWMgdm91cyBhdXggaG9yYWlyZXMgc3VpdmFudHMgOjxicj4NCjxicj4NCi0gSmV1ZGkgMTIgRsOpdnJpZXIgw6AgMTI6MzA8YnI-DQotIFZlbmRyZWRpIDEzIEbDqXZyaWVyIMOgIDEyOjMwPGJyPg0KLSBMdW5kaSAxNiBGw6l2cmllciDDoCAxMjozMDxicj4NCi0gTWVyY3JlZGkgMTggRsOpdnJpZXIgw6AgMTI6MzA8YnI-DQo8YnI-DQo8YnI-DQpRdWVsIGhvcmFpcmUgdm91cyBjb252aWVudCBsZSBtaWV1eCA_PG86cD48L286cD48L3A-DQo8L2Rpdj4NCjxkaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48aW1nIGlkPSJfeDAwMDBfaTEwMjUiIHNyYz0iaHR0cHM6Ly9tYWlsZm9vZ2FlLmFwcHNwb3QuY29tL3Q_c2VuZGVyPWFhblZzYVdWQWFuVnNhV1ZrWlhOckxtTnZiUSUzRCUzRCZhbXA7dHlwZT16ZXJvY29udGVudCZhbXA7Z3VpZD02NDcxZGFjYS0wODYwLTQ3YWEtYTlkZS1iYjI5MzFlOTUxMWYiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6Ny41cHQ7Zm9udC1mYW1pbHk6JnF1b3Q7R2FkdWdpJnF1b3Q7LHNhbnMtc2VyaWY7Y29sb3I6d2hpdGUiPuGQpzwvc3Bhbj48bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPC9kaXY-DQo8ZGl2Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI-PGJyIGNsZWFyPSJhbGwiPg0KPG86cD48L286cD48L3A-DQo8ZGl2Pg0KPGRpdj4NCjxkaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj5Db3JkaWFsZW1lbnQsPG86cD48L286cD48L3A-DQo8ZGl2Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI-PG86cD4mbmJzcDs8L286cD48L3A-DQo8L2Rpdj4NCjxkaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj5KdWxpZTxvOnA-PC9vOnA-PC9wPg0KPC9kaXY-DQo8L2Rpdj4NCjwvZGl2Pg0KPC9kaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48bzpwPiZuYnNwOzwvbzpwPjwvcD4NCjxkaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj4yMDE1LTAxLTE0IDE3OjI4IEdNVCYjNDM7MDE6MDAgVmluY2VudCBLbGluZ2JlaWwgJmx0OzxhIGhyZWY9Im1haWx0bzp2aW5jZW50a2xpbmdiZWlsQGFtZXRpeC5jb20iIHRhcmdldD0iX2JsYW5rIj52aW5jZW50a2xpbmdiZWlsQGFtZXRpeC5jb208L2E-Jmd0Ozo8bzpwPjwvbzpwPjwvcD4NCjxibG9ja3F1b3RlIHN0eWxlPSJib3JkZXI6bm9uZTtib3JkZXItbGVmdDpzb2xpZCAjQ0NDQ0NDIDEuMHB0O3BhZGRpbmc6MGNtIDBjbSAwY20gNi4wcHQ7bWFyZ2luLWxlZnQ6NC44cHQ7bWFyZ2luLXJpZ2h0OjBjbSI-DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI-SGVsbG8gU2FuZHJhLDxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI-SnVsaWUgKGVuIGNjKSB2YSB0ZSBwcm9wb3NlciBkZXMgZGlzcG9zIHBvdXIgdW4gZGVqLjxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj48YnI-DQpBIHRyw6hzIGJpZW50w7R0LDxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI-Jm5ic3A7PG86cD48L286cD48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdpbi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG8iPiZuYnNwOzxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvO2JhY2tncm91bmQ6d2hpdGUiPg0KPGI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMC4wcHQ7Y29sb3I6IzVFNUU1RSI-LS0tPC9zcGFuPjwvYj48bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0bztiYWNrZ3JvdW5kOndoaXRlIj4NCjxiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTAuMHB0O2NvbG9yOiM1RTVFNUUiPiZuYnNwOzwvc3Bhbj48L2I-PG86cD48L286cD48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdpbi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG87YmFja2dyb3VuZDp3aGl0ZSI-DQo8Yj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjEwLjBwdDtjb2xvcjojNUU1RTVFIj5WaW5jZW50IEtMSU5HQkVJTCAmbmJzcDs8aW1nIGJvcmRlcj0iMCIgd2lkdGg9IjE3IiBoZWlnaHQ9IjE3IiBpZD0iX3gwMDAwX2kxMDI2IiBzcmM9ImNpZDppbWFnZTAwMS5qcGdAMDFEMDMwMjAuQTgxRDNFQzAiIGFsdD0iY2lkOmltYWdlMDAzLmpwZ0AwMUNGMURCMi5CRkIxNUQ5MCI-IERpcmVjdGV1ciBBc3NvY2nDqTwvc3Bhbj48L2I-PG86cD48L286cD48L3A-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdpbi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG87YmFja2dyb3VuZDp3aGl0ZSI-DQo8c3BhbiBzdHlsZT0iZm9udC1zaXplOjkuMHB0O2NvbG9yOiM1RTVFNUUiPjExLCBydWUgUm91Z2Vtb250IC0gNzUwMDkgUGFyaXM8L3NwYW4-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMC4wcHQ7Y29sb3I6YmxhY2siPjxicj4NCjwvc3Bhbj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjkuMHB0O2NvbG9yOiM1RTVFNUUiPlRlbCZuYnNwOyAmbmJzcDs6IDAxIDg0IDE2IDE2IDU1PC9zcGFuPjxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvO2JhY2tncm91bmQ6d2hpdGUiPg0KPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTo5LjBwdDtjb2xvcjojNUU1RTVFIj5Nb2IgOiAwNiAyMiA3MiA4MyAxNzxicj4NCkUtbWFpbCA6IDwvc3Bhbj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjkuMHB0O2NvbG9yOiM1QjlCRDUiPjxhIGhyZWY9Im1haWx0bzp2aW5jZW50LmtsaW5nYmVpbEBhbWV0aXguY29tIiB0YXJnZXQ9Il9ibGFuayI-PHNwYW4gc3R5bGU9ImNvbG9yOiM1QjlCRDUiPnZpbmNlbnQua2xpbmdiZWlsQGFtZXRpeC5jb208L3NwYW4-PC9hPg0KPC9zcGFuPjxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvO2JhY2tncm91bmQ6d2hpdGUiPg0KPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTo5LjBwdDtjb2xvcjojNUU1RTVFIj5TaXRlIDogPC9zcGFuPjxzcGFuIHN0eWxlPSJmb250LXNpemU6OS4wcHQ7Y29sb3I6IzVCOUJENSI-PGEgaHJlZj0iaHR0cDovL3d3dy5hbWV0aXguY29tLyIgdGFyZ2V0PSJfYmxhbmsiPjxzcGFuIHN0eWxlPSJjb2xvcjojNUI5QkQ1Ij5odHRwOi8vd3d3LmFtZXRpeC5jb208L3NwYW4-PC9hPg0KPC9zcGFuPjxvOnA-PC9vOnA-PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMC41cHQ7Y29sb3I6YmxhY2siPjxpbWcgYm9yZGVyPSIwIiB3aWR0aD0iMjM2IiBoZWlnaHQ9IjEwNCIgaWQ9Il94MDAwMF9pMTAyNyIgc3JjPSJjaWQ6aW1hZ2UwMDIucG5nQDAxRDAzMDIwLkE4MUQzRUMwIiBhbHQ9ImNpZDppbWFnZTAwNC5wbmdAMDFDRjFEQjIuQkZCMTVEOTAiPjwvc3Bhbj48bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI-Jm5ic3A7PG86cD48L286cD48L3A-DQo8L2Rpdj4NCjwvZGl2Pg0KPC9ibG9ja3F1b3RlPg0KPC9kaXY-DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj48bzpwPiZuYnNwOzwvbzpwPjwvcD4NCjwvZGl2Pg0KPC9kaXY-DQo8L2JvZHk-DQo8L2h0bWw-DQo="}}]}, {:partId=>"1", :mimeType=>"image/jpeg", :filename=>"image001.jpg", :headers=>[{:name=>"Content-Type", :value=>"image/jpeg; name=\"image001.jpg\""}, {:name=>"Content-Description", :value=>"image001.jpg"}, {:name=>"Content-Disposition", :value=>"inline; filename=\"image001.jpg\"; size=745; creation-date=\"Wed, 14 Jan 2015 16:41:12 GMT\"; modification-date=\"Wed, 14 Jan 2015 16:41:12 GMT\""}, {:name=>"Content-ID", :value=>""}, {:name=>"Content-Transfer-Encoding", :value=>"base64"}], :body=>{:attachmentId=>"ANGjdJ9EUce9luxTns6YCguM66uWOxWwYQvDeG0x_b--7WtuX286rMcFGJCzxreUbo81SqPcPmi_SH4Pwj5n6OhAGDh1U2vogEB2JE13baFrUObxecXTeaaO6jeq78j7dv6ZtY0ivtRdQQKFDddIctZ2xDYJ7YuMNm1LDpuWGR5f_yNIDL-9t5EQY8FWEGhcXAZOS_JSxqc5v469Mxt9bRK6-r-8I7p6C9kEtxm6s1DMpEMaV0veKGU7Fcu7kpO7_6xoCc16fMGDWnlX5tmRv1KGopc9E5WQoq453aFAig", :size=>1024}}, {:partId=>"2", :mimeType=>"image/png", :filename=>"image002.png", :headers=>[{:name=>"Content-Type", :value=>"image/png; name=\"image002.png\""}, {:name=>"Content-Description", :value=>"image002.png"}, {:name=>"Content-Disposition", :value=>"inline; filename=\"image002.png\"; size=18240; creation-date=\"Wed, 14 Jan 2015 16:41:13 GMT\"; modification-date=\"Wed, 14 Jan 2015 16:41:13 GMT\""}, {:name=>"Content-ID", :value=>""}, {:name=>"Content-Transfer-Encoding", :value=>"base64"}], :body=>{:attachmentId=>"ANGjdJ9ZpRLg9NX7RpBGCcmbfrARNV5R3BJLY2O5jKfpgfpe1rntEYXw0Vr0bIYMMU9DJ3jc-ofm2NNsKeu5wkjB3IglrGe19BHrgjWh9Mz_65H-Lp3x_avlwVg4Tm1lmyWF6POmK5BN64vlwQGcSJbaytrRI6PuRVeLLBWI32xfllZi-N_Pf9JNPgNouf_jVP8XFzl9pPD6gIaUugsMNdUXPEyx0pY00JOhgkP1P0Wcz6EU931R9Au19mYdO87ROzdBAea7ekqA3xHT5E7XpnC5O9XyJAray1D9iPWVcQ", :size=>24960}}]}, :sizeEstimate=>44153} 23 | end 24 | 25 | def test_draft_list(params={}) 26 | {:drafts=>[{:id=>"1490204870554280932", :message=>{:id=>"14ae456f2ff1e3e4", :threadId=>"14ae456f2ff1e3e4"}}, {:id=>"1490204870554280939", :message=>{:id=>"14ae456f2ff1e3e5", :threadId=>"14ae456f2ff1e3e5"}}], :resultSizeEstimate=>2}.merge(params) 27 | end 28 | 29 | def test_label params={} 30 | { 31 | :id=>"CATEGORY_UPDATES", 32 | :name=>"CATEGORY_UPDATES", 33 | :messageListVisibility=>"hide", 34 | :labelListVisibility=>"labelHide", 35 | :type=>"system", 36 | :messagesTotal=>129, 37 | :messagesUnread=>33, 38 | :threadsTotal=>105, 39 | :threadsUnread=>21 40 | }.merge(params) 41 | end 42 | 43 | def test_label_list(params={}) 44 | {:labels=>[{:id=>"CATEGORY_UPDATES", :name=>"CATEGORY_UPDATES", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"UNREAD", :name=>"UNREAD", :type=>"system"}, {:id=>"DRAFT", :name=>"DRAFT", :messageListVisibility=>"hide", :labelListVisibility=>"labelShow", :type=>"system"}, {:id=>"INBOX", :name=>"INBOX", :messageListVisibility=>"hide", :labelListVisibility=>"labelShow", :type=>"system"}, {:id=>"CATEGORY_PROMOTIONS", :name=>"CATEGORY_PROMOTIONS", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"CATEGORY_SOCIAL", :name=>"CATEGORY_SOCIAL", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"CATEGORY_PERSONAL", :name=>"CATEGORY_PERSONAL", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"CATEGORY_FORUMS", :name=>"CATEGORY_FORUMS", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"TRASH", :name=>"TRASH", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"IMPORTANT", :name=>"IMPORTANT", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}, {:id=>"SENT", :name=>"SENT", :messageListVisibility=>"hide", :labelListVisibility=>"labelShow", :type=>"system"}, {:id=>"STARRED", :name=>"STARRED", :messageListVisibility=>"hide", :labelListVisibility=>"labelShow", :type=>"system"}, {:id=>"SPAM", :name=>"SPAM", :messageListVisibility=>"hide", :labelListVisibility=>"labelHide", :type=>"system"}]}.merge(params) 45 | end 46 | 47 | def test_thread(params = {}) 48 | {:id=>"14ae456f2ff1e3e4", :historyId=>"227960", :messages=>[ 49 | {:id=>"14ae456f2ff1e3e4", :threadId=>"14ae456f2ff1e3e4", :labelIds=>["DRAFT"], :snippet=>"sd Cordialement, Julie", :historyId=>"227960", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"MIME-Version", :value=>"1.0"}, {:name=>"Received", :value=>"by 10.64.21.4 with HTTP; Tue, 13 Jan 2015 09:28:31 -0800 (PST)"}, {:name=>"Date", :value=>"Tue, 13 Jan 2015 18:28:31 +0100"}, {:name=>"Message-ID", :value=>""}, {:name=>"Subject", :value=>""}, {:name=>"From", :value=>"Julie Desk "}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=f46d042abf34a36bd9050c8bf335"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}], :body=>{:size=>28, :data=>"c2QNCkNvcmRpYWxlbWVudCwNCg0KSnVsaWUNCg=="}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}], :body=>{:size=>154, :data=>"PGRpdiBkaXI9Imx0ciI-c2Q8YnIgY2xlYXI9ImFsbCI-PGRpdj48ZGl2IGNsYXNzPSJnbWFpbF9zaWduYXR1cmUiPjxkaXYgZGlyPSJsdHIiPkNvcmRpYWxlbWVudCw8ZGl2Pjxicj48L2Rpdj48ZGl2Pkp1bGllPC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-DQo8L2Rpdj4NCg=="}}]}, :sizeEstimate=>710}, 50 | {:id=>"14ae456f2ff1e3e4", :threadId=>"14ae456f2ff1e3e4", :labelIds=>["SENT"], :snippet=>"sd Cordialement, Julie", :historyId=>"227960", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"MIME-Version", :value=>"1.0"}, {:name=>"Received", :value=>"by 10.64.21.4 with HTTP; Tue, 13 Jan 2015 09:28:31 -0800 (PST)"}, {:name=>"Date", :value=>"Tue, 13 Jan 2015 18:28:31 +0100"}, {:name=>"Message-ID", :value=>""}, {:name=>"Subject", :value=>""}, {:name=>"From", :value=>"Julie Desk "}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=f46d042abf34a36bd9050c8bf335"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}], :body=>{:size=>28, :data=>"c2QNCkNvcmRpYWxlbWVudCwNCg0KSnVsaWUNCg=="}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}], :body=>{:size=>154, :data=>"PGRpdiBkaXI9Imx0ciI-c2Q8YnIgY2xlYXI9ImFsbCI-PGRpdj48ZGl2IGNsYXNzPSJnbWFpbF9zaWduYXR1cmUiPjxkaXYgZGlyPSJsdHIiPkNvcmRpYWxlbWVudCw8ZGl2Pjxicj48L2Rpdj48ZGl2Pkp1bGllPC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-DQo8L2Rpdj4NCg=="}}]}, :sizeEstimate=>710}, 51 | {:id=>"14ae456f2ff1e3e4", :threadId=>"14ae456f2ff1e3e4", :labelIds=>["UNREAD"], :snippet=>"sd Cordialement, Julie", :historyId=>"227960", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"MIME-Version", :value=>"1.0"}, {:name=>"Received", :value=>"by 10.64.21.4 with HTTP; Tue, 13 Jan 2015 09:28:31 -0800 (PST)"}, {:name=>"Date", :value=>"Tue, 13 Jan 2015 18:28:31 +0100"}, {:name=>"Message-ID", :value=>""}, {:name=>"Subject", :value=>""}, {:name=>"From", :value=>"Julie Desk "}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=f46d042abf34a36bd9050c8bf335"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}], :body=>{:size=>28, :data=>"c2QNCkNvcmRpYWxlbWVudCwNCg0KSnVsaWUNCg=="}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}], :body=>{:size=>154, :data=>"PGRpdiBkaXI9Imx0ciI-c2Q8YnIgY2xlYXI9ImFsbCI-PGRpdj48ZGl2IGNsYXNzPSJnbWFpbF9zaWduYXR1cmUiPjxkaXYgZGlyPSJsdHIiPkNvcmRpYWxlbWVudCw8ZGl2Pjxicj48L2Rpdj48ZGl2Pkp1bGllPC9kaXY-PC9kaXY-PC9kaXY-PC9kaXY-DQo8L2Rpdj4NCg=="}}]}, :sizeEstimate=>710}, 52 | 53 | ]}.merge(params) 54 | end 55 | 56 | def test_thread_list 57 | {:threads=>[{:id=>"14ae456f2ff1e3e4", :snippet=>"", :historyId=>"227960"}, {:id=>"14ae4438ed604170", :snippet=>"", :historyId=>"227955"}, {:id=>"14ac91101e18bed8", :snippet=>"", :historyId=>"227646"}, {:id=>"14ae3d759a2166d1", :snippet=>"", :historyId=>"227565"}, {:id=>"14acfd71d2f20308", :snippet=>"", :historyId=>"227430"}], :nextPageToken=>"11260038910369504656", :resultSizeEstimate=>25} 58 | end 59 | 60 | def test_message(params = {}) 61 | {:id=>"14ada61b92db5e63", :threadId=>"14ada58320652fc7", :labelIds=>["IMPORTANT", "CATEGORY_PERSONAL"], :snippet=>"Bonsoir David Merci pour votre retour. Je verrai demain avec Julie, quand fixer un RDV téléphonique.", :historyId=>"219674", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"Delivered-To", :value=>"julie@juliedesk.com"}, {:name=>"Received", :value=>"by 10.64.89.39 with SMTP id bl7csp849585ieb; Sun, 11 Jan 2015 11:04:05 -0800 (PST)"}, {:name=>"X-Received", :value=>"by 10.180.75.237 with SMTP id f13mr23178958wiw.69.1421003044898; Sun, 11 Jan 2015 11:04:04 -0800 (PST)"}, {:name=>"Return-Path", :value=>""}, {:name=>"Received", :value=>"from mail-wg0-x232.google.com (mail-wg0-x232.google.com. [2a00:1450:400c:c00::232]) by mx.google.com with ESMTPS id o4si9737386wia.79.2015.01.11.11.04.04 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Sun, 11 Jan 2015 11:04:04 -0800 (PST)"}, {:name=>"Received-SPF", :value=>"pass (google.com: domain of arlenebentolila@gmail.com designates 2a00:1450:400c:c00::232 as permitted sender) client-ip=2a00:1450:400c:c00::232;"}, {:name=>"Authentication-Results", :value=>"mx.google.com; spf=pass (google.com: domain of arlenebentolila@gmail.com designates 2a00:1450:400c:c00::232 as permitted sender) smtp.mail=arlenebentolila@gmail.com; dkim=pass header.i=@gmail.com; dmarc=pass (p=NONE dis=NONE) header.from=gmail.com"}, {:name=>"Received", :value=>"by mail-wg0-f50.google.com with SMTP id a1so15938647wgh.9 for ; Sun, 11 Jan 2015 11:04:04 -0800 (PST)"}, {:name=>"DKIM-Signature", :value=>"v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=content-type:mime-version:subject:from:in-reply-to:date:cc :content-transfer-encoding:message-id:references:to; bh=mEmKhvWjFpq+DnO0xxdPjpd/i7MTfctQp+prGlYASc4=; b=ouf26RPtOvk9wATJjcDxdOET2WD58x6efOOd8sZXt9c09mCoAsocVHyihpWWacId9j qtuhKv/bTOe8AbMnk3JU4PHgnyJyCDL3gRjUrnlzMmwb92gd2c6TxS/p711fYvDfrz+L jyABwocRbJSBTH/YsGPh/sP/z89oE0Aox5QIvd7PE1ykUWIhk4GVhGx7qf/SEImA4oGB zb17MgSVHx7s9glVeibxturDZXJPSPS/sFX4VgHtMT4GlKvjtiLKJl3G+rIAbV+YsIet zmcrurpZM97/ztnL/8IrQG17Kqe+JqanNlVwvhEMfTLOxooC+5p1v1tdHvN+8RMAcYWv QyrQ=="}, {:name=>"X-Received", :value=>"by 10.194.175.102 with SMTP id bz6mr2702286wjc.120.1421003043927; Sun, 11 Jan 2015 11:04:03 -0800 (PST)"}, {:name=>"Return-Path", :value=>""}, {:name=>"Received", :value=>"from [192.168.0.39] (fes75-2-78-192-50-38.fbxo.proxad.net. [78.192.50.38]) by mx.google.com with ESMTPSA id i15sm18583199wjq.22.2015.01.11.11.04.02 (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Sun, 11 Jan 2015 11:04:02 -0800 (PST)"}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=Apple-Mail-47C5C329-5673-4226-945A-D9F99028CBB5"}, {:name=>"Mime-Version", :value=>"1.0 (1.0)"}, {:name=>"Subject", :value=>"Re: Recommandation Sup de Luxe pour sujet de mémoire Arlène Bentolila"}, {:name=>"From", :value=>"\"Arlène Bentolila\" "}, {:name=>"X-Mailer", :value=>"iPhone Mail (12B440)"}, {:name=>"In-Reply-To", :value=>""}, {:name=>"Date", :value=>"Sun, 11 Jan 2015 20:04:01 +0100"}, {:name=>"Cc", :value=>"Julie Desk "}, {:name=>"Bcc", :value=>"Julien "}, {:name=>"Content-Transfer-Encoding", :value=>"7bit"}, {:name=>"Message-Id", :value=>""}, {:name=>"References", :value=>" "}, {:name=>"To", :value=>"David Alexandre Klingbeil "}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=utf-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>6912, :data=>"Qm9uc29pciBEYXZpZA0KDQpNZXJjaSBwb3VyIHZvdHJlIHJldG91ci4gSmUgdmVycmFpIGRlbWFpbiBhdmVjIEp1bGllLCBxdWFuZCBmaXhlciB1biBSRFYgdMOpbMOpcGhvbmlxdWUuIA0KDQpQb3VyIGluZm9ybWF0aW9uLCBqJ2FpIGFqdXN0w6kgbW9uIHN1amV0IGRlIG3DqW1vaXJlIHF1ZSBqZSB0cm91dmFpcyB0cm9wIGNsYXNzaXF1ZSBldCBjZSwgYXByw6hzIGF2b2lyIMOpY2hhbmfDqSBhdmVjIGRlcyBjb25zdWx0YW50cy4gIA0KSmUgdm91cyBsJ2V4cG9zZSBjaS1kZXNzb3VzLiBWb3MgY29uc2VpbHMgZXQgcmVtYXJxdWVzIG1lIHNlcm9udCB0cmVzIHV0aWxlcy4gIA0KDQpTdWpldCAoZW52b3nDqSDDoCBTdXAgZGUgTHV4ZSBhdmFudCBoaWVyLCBWZW5kcmVkaSk6IA0KDQpDb21tZW50IGxlcyBtYXJxdWVzIGRlIG1vZGUgZXQgZGUgam9haWxsZXJpZSBzJ3kgcHJlbm5lbnQgcG91ciBhcHByw6loZW5kZXIgbGVzIG9iamV0cyBjb25uZWN0w6lzIGRhbnMgbGV1ciBzdHJhdMOpZ2llIGQnb2ZmcmU_IA0KSmUgdmV1eCBkw6ltb250cmVyIHF1ZSBsZXMgb2JqZXRzIGNvbm5lY3TDqXMgc29udCB1biBsZXZpZXIgZGUgY3JvaXNzYW5jZSBldCBkZSBkaWZmw6lyZW5jaWF0aW9uLiBKZSB2ZXV4IGFuYWx5c2VyIGxlcyBjb21wb3J0ZW1lbnRzIGRlcyBtYXJxdWVzIGZhY2Ugw6AgY2V0IGVuamV1eDogdm9udCAtIGVsbGVzIHJlc3TDqWVzIGRhbnMgbGUgY2xhc3NpY2lzbWUgYXZlYyBkZXMgbWF0acOocmVzIG5vYmxlcyBwYXIgZXhlbXBsZSBvdSDDqXZvbHVlciBkZSBmYcOnb24gc2lnbmlmaWNhdGl2ZXMgYXZlYyBsZXMgb2JqZXRzIGNvbm5lY3TDqXM_IENvbW1lbnQgYXBwcsOpaGVuZGVudCAtIGVsbGVzIGNlIGNoYW5nZW1lbnQgZGlnaXRhbCBkYW5zIGxldXIgc3RyYXTDqWdpZSBk4oCZb2ZmcmUgZXQgZGUgZMOpdmVsb3BwZW1lbnQ_DQoNCkJpZW4gw6Agdm91cyANCkFybMOobmUNCg0KQXJsw6huZSBCZW50b2xpbGENCkVudm95w6kgZGUgbW9uIGlQaG9uZS4NCjA2IDYyIDE4IDQ3IDA4DQoNCg0KPiBMZSAxMSBqYW52LiAyMDE1IMOgIDE5OjUzLCBEYXZpZCBBbGV4YW5kcmUgS2xpbmdiZWlsIDxkYWtAZHltYW50LmNvbT4gYSDDqWNyaXQgOg0KPiANCj4gQm9uc29pciBBcmxlbmUsDQo-IA0KPiBQYXJsb25zLWVuIHBhciB0w6lsw6lwaG9uZSBjZXR0ZSBzZW1haW5lIG91IGxhIHN1aXZhbnRlDQo-IA0KPiBKdWxpZSAoZW4gY2MpIHZhIHZvdXMgY29udGFjdGVyIHBvdXIgdHJvdXZlciB1biBjcsOpbmVhdSBkZSAzMG1pbiBwb3VyIHVuIGNhbGwuDQo-IA0KPiBQYXNzZXogdW5lIGV4Y2VsbGVudGUgZmluIGRlIHdlZWstZW5kLA0KPiANCj4gQSBiaWVudMO0dCwNCj4gDQo-IERhdmlkDQo-IA0KPiBEYXZpZCBBbGV4YW5kcmUgS2xpbmdiZWlsDQo-IENFTyAmIENvLUZvdW5kZXIgfCBEeW1hbnQuY29tDQo-IC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4NCj4gTS4gZGFrQGR5bWFudC5jb20NCj4gVCAuICszMyA2IDYzIDk0IDc3IDQzDQo-IEBEYXZpZEtsaW5nYmVpbA0KPiAuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uDQo-IA0KPiANCj4gTGUgNSBqYW52aWVyIDIwMTUgMTM6MTksIEFybGVuZSBCZW50b2xpbGEgPGFybGVuZWJlbnRvbGlsYUBnbWFpbC5jb20-IGEgw6ljcml0IDoNCj4-IENoZXIgTW9uc2lldXIsDQo-PiANCj4-IEonYWkgbGUgcGxhaXNpciBkZSB2b3VzIGNvbnRhY3RlciBzdXIgbGVzIGNvbnNlaWxzIGRlIE1hZGFtZSBsZSBHb3VndWVjLCBEaXJlY3RyaWNlIHDDqWRhZ29naXF1ZSDDoCBTdXAgZGUgTHV4ZSBkYW5zIGxlIGNhZHJlIGRlIGxhIHByw6lwYXJhdGlvbiBkZSBtb24gbcOpbW9pcmUuIENlbHVpIC0gY2kgZG9pdCDDqnRyZSB1biByYXBwb3J0IHByb2Zlc3Npb25uZWwgY29uY3JldCBldCBwZXJ0aW5lbnQgZXQgbm9uIHBhcyAiYWNhZMOpbWlxdWUuIEplIHZvdXMgcHJvcG9zZSBkJ2VuIGTDqWNvdXZyaXIgbGUgc3VqZXQgZW4gcGnDqGNlIGpvaW50ZSBldCBsYSByw6lwb25zZSBkZSBTdXAgZGUgTHV4ZSBlbiBiYXMgZGUgY2UgbWFpbC4gDQo-PiANCj4-IERhbnMgbGEgcGVyc3BlY3RpdmUgZGUgdm91cyByZW5jb250cmVyLCBqZSBtZSBwcm9wb3NlIGRlIHZvdXMgcsOpc3VtZXIgbW9uIHBhcmNvdXJzIGV0IG1vbiBvYmplY3RpZiBwcm9mZXNzaW9ubmVscyBlbiByYXBwb3J0IGF2ZWMgbW9uIG3DqW1vaXJlLiANCj4-IA0KPj4gUGFyY291cnMNCj4-IEonYWkgdW5lIGV4cMOpcmllbmNlIHByb2Zlc3Npb25uZWxsZSBkZSBwcsOocyBkZSAxNSBhbnMgZW4gdGFudCBxdWUgRGlyZWN0cmljZSBkZSBjbGllbnTDqGxlIHByZXNzZSDDqWNyaXRlIGF1IHNlaW4gZGUgZ3JhbmRzIGdyb3VwZXMgbcOpZGlhcyAoVMOpbMOpcmFtYSwgTGFnYXJkw6hyZSkuIEonYWkgc3DDqWNpYWxpc8OpIGNldHRlIGV4cMOpcmllbmNlIGNvbW1lcmNpYWxlIGRlcHVpcyA4IGFucyBzdXIgbGVzIHNlY3RldXJzIGR1IGx1eGUgZXQgZGUgbGEgbW9kZSBlbiB0cmF2YWlsbGFudCBzdXIgbGVzIHByb2pldHMgZGUgY29tbXVuaWNhdGlvbiBwdWJsaWNpdGFpcmUgZGVzIGFubm9uY2V1cnMgZMOpZGnDqXMgZXQgcG91ciBkZXMgbWFycXVlcyBkZSBwcmVzc2UgaGF1dCBkZSBnYW1tZSBldCBsdXhlIChFbGxlLCBOdW3DqXJvLjs7KS4gTW9uIENWIGVzdCBlbiBwacOoY2Ugam9pbnRlLg0KPj4gDQo-PiBQcm9qZXQgcHJvZmVzc2lvbm5lbA0KPj4gSmUgdmV1eCBhbGxleiBwbHVzIGxvaW4gZGFucyBtb24gZMOpdmVsb3BwZW1lbnQgZGUgbWFycXVlcyBkZSBsdXhlIHNvaXQgYXUgbml2ZWF1IGNvbW1lcmNpYWwgc29pdCBhdSBuaXZlYXUgZGUgbGEgY29tbXVuaWNhdGlvbiBldCBjJ2VzdCBwb3VycXVvaSBqJ2FpIGludMOpZ3LDqSBTdXAgZGUgTHV4ZS4gSmUgdmlzZSBwb3VyIHNlcHRlbWJyZSBhdSBwbHVzIHRhcmQsIHVuZSBhY3Rpdml0w6kgY29tbWVyY2lhbGUgYXUgc2VpbiBkJ3VuZSBtYXJxdWUgZGUgbHV4ZS9tb2RlIG91IGVuIGFnZW5jZSAoZMOpdmVsb3BwZW1lbnQgb3UgY29uc2VpbCkuIFBvdXIgeSBwYXJ2ZW5pciBldCBlbnJpY2hpciBtYSB2YWxldXIgc3VyIGxlIG1hcmNow6ksIGplIGRvaXMgY29uc29saWRlciBjZSBxdWkgcydpbXBvc2UsIGxlIGRpZ2l0YWwhDQo-PiANCj4-IE3DqW1vaXJlIC0gcmFwcG9ydCBTdXAgZGUgTHV4ZQ0KPj4gSidhaSBkb25jIG9wdMOpIHBvdXIgdW4gc3VqZXQgZGUgbcOpbW9pcmUgcXVpIHBvcnRlIHN1ciBsZSBsdXhlIGTDqWZpw6kgcGFyIGxlIGRpZ2l0YWwuIEplIHZldXggZMOpbW9udHJlciBxdWUgbGUgbHV4ZSBlc3QgcsOpdGljZW50IGZhY2UgYXUgZGlnaXRhbCwgcG91cnRhbnQgZMOpY2lzaWYgZGFucyBsJ2FwcG9ydCBkZSBub3V2ZWF1eCBzZXJ2aWNlcyBldCBkYW5zIGxlIGTDqXBsb2llbWVudCBkdSBsdXhlLiAgDQo-PiBNYWlzIG1vbiBzdWpldCBlc3QgdHJvcCBsYXJnZS4gSmUgbmUgcGV1eCB0cmFpdGVyIGRlIHRvdXMgbGVzIHNlcnZpY2VzIGFwcG9ydMOpcyBwYXIgbGUgZGlnaXRhbC4gSidow6lzaXRlIGVudHJlIGxhIGNvbW11bmljYXRpb24gZXQgbGEgc3RyYXTDqWdpZSBkJ29mZnJlIChwcm9kdWl0cykuIFF1ZWwgZXN0IGxlIHBsdXMgInV0aWxlIGQnYXByw6hzIHZvdXMiPw0KPj4gRGUgcGx1cyBqZSBuZSBwZXV4IHRyYWl0ZXIgZHUgbHV4ZSBlbiBnw6luw6lyYWwuIEplIGRvaXMgY2hvaXNpciB1biBzZWdtZW50IGRlIGNlbHVpLWNpIC4gSmUgc291aGFpdGUgZm9jYWxpc2VyIHN1ciBsYSBqb2FpbGxlcmllIHF1aSBtZSBzZW1ibGUgcGx1cyBkw6lmacOpZSBwYXIgbGUgZGlnaXRhbCBjYXIgY2Ugc29udCBkZXV4ICJtYXJjaMOpcyIgYSBwcmlvcmkgYW50aW5vbWlxdWVzLiBDb21wcmVuZHJlIGxhIGNvbXBsZXhpdMOpIGR1IGR1byBKb2FpbGxlcmllIC8gZGlnaXRhbCBtZSBzZW1ibGUgw6lnYWxlbWVudCBwbHVzIGTDqXRlcm1pbmFudCBwb3VyIGNvbXByZW5kcmUgbGVzIGVuamV1eCBwb3PDqXMgcGFyIGxlIGRpZ2l0YWwuIFF1J2VuIHBlc25lei12b3VzPw0KPj4gDQo-PiBKZSB2b3VzIHJlbWVyY2llIHBhciBhdmFuY2UgcG91ciBsJ2F0dGVudGlvbiBxdWUgdm91cyBhY2NvcmRlcmV6IMOgIG1vbiBwcm9qZXQgZXQgamUgdm91cyBwcm9wb3NlIGRlIGNvbnZlbmlyIGQndW4gcmVuZGV6IC0gdm91cyBwb3VyIGF2YW5jZXIgc3VyIGNlbHVpLWNpLiBKZSBzdWlzIGRpc3BvbmlibGUgY2V0dGUgc2VtYWluZSDDoCB2b3RyZSBjb252ZW5hbmNlIGV0IHZvdXMgcG91dmV6IG3igJlhcHBlbGVyIGF1IDA2IDYyIDE4IDQ3IDA4Lg0KPj4gDQo-PiBCaWVuIMOgIHZvdXMsDQo-PiANCj4-IEFybMOobmUgQmVudG9saWxhDQo-PiANCj4-IA0KPj4gLS0tLS0tLS0tLSBNZXNzYWdlIHRyYW5zZsOpcsOpIC0tLS0tLS0tLS0NCj4-IERlIDogR2hpc2xhaW5lIExlIEdvdWd1ZWMgPGdsZWdvdWd1ZWNAZWRjcGFyaXMuZWR1Pg0KPj4gRGF0ZSA6IDE5IGTDqWNlbWJyZSAyMDE0IDE5OjQ2DQo-PiBPYmpldCA6IFJFOiBTdWpldCBkZSBtw6ltb2lyZSB1bmlxdWUgQXJsw6huZSBldCAiZXhlcmNpY2UiIDogamUgdmV1eCBkw6ltb250cmVyIHF1ZQ0KPj4gw4AgOiBBcmxlbmUgQmVudG9saWxhIDxhcmxlbmViZW50b2xpbGFAZ21haWwuY29tPg0KPj4gQ2MgOiB0ZGVsYXJpdmllcmUgPHRkZWxhcml2aWVyZUBlZGNwYXJpcy5lZHU-DQo-PiANCj4-IA0KPj4gQ2jDqHJlIEFybMOobmUsDQo-PiANCj4-IE1lcmNpIHBvdXIgY2VzIGluZm9ybWF0aW9ucy4NCj4-IA0KPj4gTm91cyBwcmVub25zIG5vdGUgZGUgdm90cmUgbm91dmVhdSBzdWpldCBkZSBtw6ltb2lyZSA6ICBMZXMgZMOpZmlzIGR1IGRpZ2l0YWwgZGFucyBsZXMgc3RyYXTDqWdpZXMgZGUgZMOpdmVsb3BwZW1lbnQgZHUgbHV4ZSBlbiBtYXRpw6hyZSAgZOKAmW9mZnJlLCBkZSBzZXJ2aWNlcywgZGUgZGlzdHJpYnV0aW9uIGV0IGRlIGNvbW11bmljYXRpb24NCj4-IA0KPj4gTm91cyBlbiB2YWxpZG9ucyBsZSB0aMOobWUgbWFpcyBwZW5zb25zIHRvdXRlZm9pcyBxdeKAmWlsIGVzdCBlbmNvcmUgdHJvcCBsYXJnZSBlbiB0ZXJtZSBkZSBww6lyaW3DqHRyZSBk4oCZYW5hbHlzZSArIGplIHBlbnNlIHF1ZSB2b3VzIGRldmV6IHZvdXMgY29uY2VudHJlciBzdXIgdW4gc2VjdGV1ciBldCBub24gbGUgbHV4ZSBlbiBnw6luw6lyYWwuDQo-PiANCj4-IFZvdXMgZGV2ZXogcmVuY29udHJlciBkZXMgcHJvZmVzc2lvbm5lbHMgcmFwaWRlbWVudCBxdWkgdm91cyBhaWRlcm9udCDDoCBkw6lmaW5pciB1bmUgcHJvYmzDqW1hdGlxdWUgcGx1cyBwcsOpY2lzZS4gTWVyY2kgZGUgY29udGFjdGVyIE3DqWxhbmllIHF1aSB2b3VzIGRvbm5lcmEgbGVzIGNvb3Jkb25uw6llcyBkZSBNb25zaWV1ciBHw6lyYWxkIEVTUEFSREVMTElFUiwgRGlyZWN0ZXVyIEFzc29jacOpIGRlIGxhIHNvY2nDqXTDqSBFTk9SQSBDb25zdWx0aW5nICsgY29udGFjdGV6IGRlIG5vdHJlIHBhcnQgRGF2aWQgQWxleGFuZHJlIEtsaW5nYmVpbCwgQ0VPICYgQ28tRm91bmRlciB8IER5bWFudC5jb20gLSAgZGFrQGR5bWFudC5jb20gKyBjbyBmb25kYXRldXIgZHUgbWFnYXppbmUgZGlnaXRhbCBXZWIgJiBMdXhlIGh0dHA6Ly93d3cud2ViYW5kbHV4ZS5jb20vYS1wcm9wb3MvDQo-PiANCj4-IEJpZW4gw6Agdm91cywNCj4-IA0KPj4gR2xHDQo-PiANCj4-ICANCj4-IA0KPj4gIA0KPj4gDQo-PiBEZSA6IEFybGVuZSBCZW50b2xpbGEgW21haWx0bzphcmxlbmViZW50b2xpbGFAZ21haWwuY29tXSANCj4-IEVudm95w6kgOiB2ZW5kcmVkaSAxOSBkw6ljZW1icmUgMjAxNCAxOToxOA0KPj4gw4AgOiBnaGlzbGFpbmUubGVnb3VndWVjQGVkY3BhcmlzLmVkdQ0KPj4gQ2MgOiB0aGliYXV0LmRlbGFyaXZpZXJlQGVkY3BhcmlzLmVkdQ0KPj4gT2JqZXQgOiBTdWpldCBkZSBtw6ltb2lyZSB1bmlxdWUgQXJsw6huZSBldCAiZXhlcmNpY2UiIDogamUgdmV1eCBkw6ltb250cmVyIHF1ZQ0KPj4gDQo-PiAgDQo-PiANCj4-IENoZXJzIHRvdXMNCj4-IA0KPj4gIA0KPj4gDQo-PiBKJ2FpIHJlcGVuc8OpIG1vbiBzdWpldCBkZSBtw6ltb2lyZSBlbiBmb25jdGlvbiBkZSBtZXMgZ2_Du3RzLCBkZSBtZXMgY29tcMOpdGVuY2VzIGV0IGRlIG1lcyBvYmplY3RpZnMgcHJvZmVzc2lvbm5lbHMuIA0KPj4gDQo-PiAgDQo-PiANCj4-IEplIHBhcnMgZG9uYyBzdXIgbGEgdmVyc2lvbiBxdWUgdm91cyBkw6ljb3V2cmlyZXogZW4gcGnDqGNlIGpvaW50ZSBldCBxdWkgcG9ydGUgc3VyIGxlcyBlbmpldXggZHUgZGlnaXRhbCBkYW5zIGxlcyBzdHJhdMOpZ2llcyBkZSBkw6l2ZWxvcHBlbWVudCBkdSBsdXhlIGVuIG1hdGnDqHJlIGQnb2ZmcmUsIGRlIHNlcnZpY2UsIGRlIGRpc3RyaWJ1dGlvbiBldCBkZSBjb21tdW5pY2F0aW9uLiANCj4-IA0KPj4gSmUgbWUgc3VpcyDDqWdhbGVtZW50IHByw6p0w6llIGF1IG7DqWNlc3NhaXJlIGV4ZXJjaWNlIGR1IDogICJqZSB2ZXV4IGTDqW1vbnRyZXIgcXVlIi4NCj4-IA0KPj4gIA0KPj4gDQo-PiBKZSByZXN0ZSDDoCB2b3RyZSDDqWNvdXRlIHBvdXIgZmluYWxpc2VyIGNldHRlIGZpY2hlIHDDqWRhZ29naXF1ZSBldCB2YWxpZGVyIG1vbiBzdWpldCBkZSBtw6ltb2lyZSBhdmFudCBsYSByZW50csOpZSBkZSBqYW52aWVyIHNpIGNlbGEgdm91cyBlc3QgcG9zc2libGUuIEplIHZhaXMgZCdvcmVzIGV0IGTDqWrDoCBjb21tZW5jZXIgbWVzIHJlY2hlcmNoZXMgZXQgZmFpcmUgdW5lIMOpYmF1Y2hlIGRlIHBsYW4gcGVuZGFudCBjZXMgdmFjYW5jZXMuIA0KPj4gDQo-PiAgDQo-PiANCj4-IEplIHZvdXMgc291aGFpdGUgZGUgdHLDqHMgYm9ubmVzIGbDqnRlcy4NCj4-IA0KPj4gIA0KPj4gDQo-PiBCaWVuIMOgIHZvdXMsDQo-PiANCj4-ICANCj4-IA0KPj4gQXJsw6huZQ0KPj4gDQo-IA0K"}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=utf-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>13612, :data=>"PGh0bWw-PGhlYWQ-PG1ldGEgaHR0cC1lcXVpdj0iY29udGVudC10eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiPjwvaGVhZD48Ym9keSBkaXI9ImF1dG8iPjxkaXY-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibWFyZ2luOiAwY20gMGNtIDAuMDAwMXB0OyI-PHNwYW4gc3R5bGU9ImJhY2tncm91bmQtY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMCk7Ij48Yj5Cb25zb2lyIERhdmlkPC9iPjwvc3Bhbj48L3A-PHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbjogMGNtIDBjbSAwLjAwMDFwdDsiPjxzcGFuIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDApOyI-PGI-PGJyPjwvYj48L3NwYW4-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtYXJnaW46IDBjbSAwY20gMC4wMDAxcHQ7Ij48Yj5NZXJjaSBwb3VyIHZvdHJlIHJldG91ci4gSmUgdmVycmFpIGRlbWFpbiBhdmVjIEp1bGllLCBxdWFuZCBmaXhlciB1biBSRFYgdMOpbMOpcGhvbmlxdWUuJm5ic3A7PC9iPjwvcD48cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibWFyZ2luOiAwY20gMGNtIDAuMDAwMXB0OyI-PGI-PGJyPjwvYj48L3A-PHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbjogMGNtIDBjbSAwLjAwMDFwdDsiPjxiPlBvdXIgaW5mb3JtYXRpb24sIGonYWkgYWp1c3TDqSBtb24gc3VqZXQgZGUgbcOpbW9pcmUgcXVlIGplIHRyb3V2YWlzIHRyb3AgY2xhc3NpcXVlIGV0IGNlLCBhcHLDqHMgYXZvaXIgw6ljaGFuZ8OpIGF2ZWMgZGVzIGNvbnN1bHRhbnRzLiAmbmJzcDs8L2I-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtYXJnaW46IDBjbSAwY20gMC4wMDAxcHQ7Ij48Yj5KZSB2b3VzIGwnZXhwb3NlIGNpLWRlc3NvdXMuIFZvcyBjb25zZWlscyBldCByZW1hcnF1ZXMgbWUgc2Vyb250IHRyZXMgdXRpbGVzLiAmbmJzcDs8L2I-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtYXJnaW46IDBjbSAwY20gMC4wMDAxcHQ7Ij48Yj48YnI-PC9iPjwvcD48cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibWFyZ2luOiAwY20gMGNtIDAuMDAwMXB0OyI-PGI-U3VqZXQgKGVudm95w6kgw6AgU3VwIGRlIEx1eGUgYXZhbnQgaGllciwgVmVuZHJlZGkpOiZuYnNwOzwvYj48L3A-PHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbjogMGNtIDBjbSAwLjAwMDFwdDsiPjxzcGFuIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDApOyI-PGI-PGJyPjwvYj48L3NwYW4-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtYXJnaW46IDBjbSAwY20gMC4wMDAxcHQ7Ij48c3BhbiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwKTsiPjxiPkNvbW1lbnQgbGVzIG1hcnF1ZXMgZGUgbW9kZSBldCBkZSBqb2FpbGxlcmllIHMneSBwcmVubmVudCBwb3VyIGFwcHLDqWhlbmRlciBsZXMgb2JqZXRzIGNvbm5lY3TDqXMgZGFucyBsZXVyIHN0cmF0w6lnaWUgZCdvZmZyZT8mbmJzcDs8L2I-PG86cD48L286cD48L3NwYW4-PC9wPjwvZGl2PjxkaXY-PHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1hcmdpbjogMGNtIDBjbSAwLjAwMDFwdDsiPjxzcGFuIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDApOyI-SmUgdmV1eCBkw6ltb250cmVyIHF1ZSBsZXMgb2JqZXRzIGNvbm5lY3TDqXMgc29udCB1biBsZXZpZXIgZGUgY3JvaXNzYW5jZSBldCBkZSBkaWZmw6lyZW5jaWF0aW9uLiBKZSB2ZXV4IGFuYWx5c2VyIGxlcyBjb21wb3J0ZW1lbnRzIGRlcyBtYXJxdWVzIGZhY2Ugw6AgY2V0IGVuamV1eDogdm9udCAtIGVsbGVzIHJlc3TDqWVzIGRhbnMgbGUgY2xhc3NpY2lzbWUgYXZlYyBkZXMgbWF0acOocmVzIG5vYmxlcyBwYXIgZXhlbXBsZSBvdSDDqXZvbHVlciBkZSBmYcOnb24gc2lnbmlmaWNhdGl2ZXMgYXZlYyBsZXMgb2JqZXRzIGNvbm5lY3TDqXM_IENvbW1lbnQgYXBwcsOpaGVuZGVudCAtIGVsbGVzIGNlIGNoYW5nZW1lbnQgZGlnaXRhbCBkYW5zIGxldXIgc3RyYXTDqWdpZSBk4oCZb2ZmcmUgZXQgZGUgZMOpdmVsb3BwZW1lbnQ_PC9zcGFuPjwvcD48cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibWFyZ2luOiAwY20gMGNtIDAuMDAwMXB0OyI-PHNwYW4gc3R5bGU9ImJhY2tncm91bmQtY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMCk7Ij48YnI-PC9zcGFuPjwvcD48cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibWFyZ2luOiAwY20gMGNtIDAuMDAwMXB0OyI-PHNwYW4gc3R5bGU9ImJhY2tncm91bmQtY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMCk7Ij5CaWVuIMOgIHZvdXMmbmJzcDs8L3NwYW4-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtYXJnaW46IDBjbSAwY20gMC4wMDAxcHQ7Ij5BcmzDqG5lPC9wPjwvZGl2Pjxicj5BcmzDqG5lIEJlbnRvbGlsYTxkaXY-RW52b3nDqSBkZSBtb24gaVBob25lLjwvZGl2PjxkaXY-MDYgNjIgMTggNDcgMDg8L2Rpdj48ZGl2Pjxicj48L2Rpdj48L2Rpdj48ZGl2Pjxicj5MZSAxMSBqYW52LiAyMDE1IMOgIDE5OjUzLCBEYXZpZCBBbGV4YW5kcmUgS2xpbmdiZWlsICZsdDs8YSBocmVmPSJtYWlsdG86ZGFrQGR5bWFudC5jb20iPmRha0BkeW1hbnQuY29tPC9hPiZndDsgYSDDqWNyaXQmbmJzcDs6PGJyPjxicj48L2Rpdj48YmxvY2txdW90ZSB0eXBlPSJjaXRlIj48ZGl2PjxkaXYgZGlyPSJsdHIiPkJvbnNvaXIgQXJsZW5lLDxkaXY-PGJyPjwvZGl2PjxkaXY-UGFybG9ucy1lbiBwYXIgdMOpbMOpcGhvbmUgY2V0dGUgc2VtYWluZSBvdSBsYSBzdWl2YW50ZTwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-SnVsaWUgKGVuIGNjKSB2YSB2b3VzIGNvbnRhY3RlciBwb3VyIHRyb3V2ZXIgdW4gY3LDqW5lYXUgZGUgMzBtaW4gcG91ciB1biBjYWxsLjwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-UGFzc2V6IHVuZSBleGNlbGxlbnRlIGZpbiBkZSB3ZWVrLWVuZCw8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PkEgYmllbnTDtHQsPC9kaXY-PGRpdj48YnI-PC9kaXY-PGRpdj5EYXZpZDwvZGl2PjwvZGl2PjxiciBjbGVhcj0iYWxsIj48ZGl2PjxkaXYgY2xhc3M9ImdtYWlsX3NpZ25hdHVyZSI-PGRpdiBkaXI9Imx0ciI-PGRpdj48Yj5EYXZpZCBBbGV4YW5kcmUgS2xpbmdiZWlsPC9iPjwvZGl2PjxkaXY-PHNwYW4gc3R5bGU9ImNvbG9yOnJnYig1MSw1MSw1MSkiPkNFTyAmYW1wOyZuYnNwOzwvc3Bhbj48Zm9udCBjb2xvcj0iIzMzMzMzMyI-Q28tRm91bmRlciB8IDxhIGhyZWY9Imh0dHA6Ly9EeW1hbnQuY29tIiB0YXJnZXQ9Il9ibGFuayI-PGZvbnQgY29sb3I9IiMzMzMzMzMiPjxiPkR5bWFudC5jb208L2I-PC9mb250PjwvYT48L2ZvbnQ-PC9kaXY-PGRpdj4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uPC9kaXY-PGRpdj5NLiA8YSBocmVmPSJtYWlsdG86ZGFrQGR5bWFudC5jb20iIHRhcmdldD0iX2JsYW5rIj5kYWtAZHltYW50LmNvbTwvYT48L2Rpdj48ZGl2PlQgLiArMzMgNiA2MyA5NCA3NyA0MzwvZGl2PjxkaXY-PGEgaHJlZj0iaHR0cDovL3d3dy50d2l0dGVyLmNvbS9kYXZpZGtsaW5nYmVpbCIgdGFyZ2V0PSJfYmxhbmsiPkBEYXZpZEtsaW5nYmVpbDwvYT48L2Rpdj48ZGl2Pi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi48L2Rpdj48ZGl2PjxpbWcgc3JjPSJodHRwOi8vd3d3LmR5bWFudC5jb20vbWVkaWEvdG1wL2NhdGFsb2cvcHJvZHVjdC9kL3kvZHltYW50LWxvZ28tbWFpbC5wbmciPjxicj48L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj4NCjxkaXYgY2xhc3M9ImdtYWlsX2V4dHJhIj48YnI-PGRpdiBjbGFzcz0iZ21haWxfcXVvdGUiPkxlIDUgamFudmllciAyMDE1IDEzOjE5LCBBcmxlbmUgQmVudG9saWxhIDxzcGFuIGRpcj0ibHRyIj4mbHQ7PGEgaHJlZj0ibWFpbHRvOmFybGVuZWJlbnRvbGlsYUBnbWFpbC5jb20iIHRhcmdldD0iX2JsYW5rIj5hcmxlbmViZW50b2xpbGFAZ21haWwuY29tPC9hPiZndDs8L3NwYW4-IGEgw6ljcml0IDo8YnI-PGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0ibWFyZ2luOjAgMCAwIC44ZXg7Ym9yZGVyLWxlZnQ6MXB4ICNjY2Mgc29saWQ7cGFkZGluZy1sZWZ0OjFleCI-PGRpdiBkaXI9Imx0ciI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij5DaGVyIE1vbnNpZXVyLDwvc3Bhbj48ZGl2Pjxicj48L2Rpdj48ZGl2PkonYWkgbGUgcGxhaXNpciBkZSB2b3VzIGNvbnRhY3RlciBzdXIgbGVzIGNvbnNlaWxzIGRlIE1hZGFtZSBsZSBHb3VndWVjLCBEaXJlY3RyaWNlIHDDqWRhZ29naXF1ZSDDoCBTdXAgZGUgTHV4ZSBkYW5zIGxlIGNhZHJlIGRlIGxhIHByw6lwYXJhdGlvbiBkZSBtb24gbcOpbW9pcmUuIENlbHVpIC0gY2kgZG9pdCDDqnRyZSB1biByYXBwb3J0IHByb2Zlc3Npb25uZWwgY29uY3JldCBldCBwZXJ0aW5lbnQgZXQgbm9uIHBhcyAiYWNhZMOpbWlxdWUuIEplIHZvdXMgcHJvcG9zZSBkJ2VuIGTDqWNvdXZyaXIgbGUgc3VqZXQgZW4gcGnDqGNlIGpvaW50ZSBldCBsYSByw6lwb25zZSBkZSBTdXAgZGUgTHV4ZSBlbiBiYXMgZGUgY2UgbWFpbC4mbmJzcDs8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PkRhbnMgbGEgcGVyc3BlY3RpdmUgZGUgdm91cyByZW5jb250cmVyLCBqZSBtZSBwcm9wb3NlIGRlIHZvdXMgcsOpc3VtZXIgbW9uIHBhcmNvdXJzIGV0IG1vbiBvYmplY3RpZiBwcm9mZXNzaW9ubmVscyBlbiByYXBwb3J0IGF2ZWMgbW9uIG3DqW1vaXJlLiZuYnNwOzwvZGl2PjxkaXY-PGJyPjwvZGl2PjxkaXY-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij48Yj5QYXJjb3VyczwvYj48L3NwYW4-PC9kaXY-PGRpdj5KJ2FpIHVuZSBleHDDqXJpZW5jZSBwcm9mZXNzaW9ubmVsbGUgZGUgcHLDqHMgZGUgMTUgYW5zIGVuIHRhbnQgcXVlIERpcmVjdHJpY2UgZGUgY2xpZW50w6hsZSBwcmVzc2Ugw6ljcml0ZSBhdSBzZWluIGRlIGdyYW5kcyBncm91cGVzJm5ic3A7bcOpZGlhcyZuYnNwOyhUw6lsw6lyYW1hLCBMYWdhcmTDqHJlKS4gSidhaSBzcMOpY2lhbGlzw6kgY2V0dGUmbmJzcDtleHDDqXJpZW5jZSBjb21tZXJjaWFsZSBkZXB1aXMgOCBhbnMgc3VyIGxlcyBzZWN0ZXVycyBkdSBsdXhlIGV0IGRlIGxhIG1vZGUgZW4gdHJhdmFpbGxhbnQgc3VyIGxlcyBwcm9qZXRzIGRlIGNvbW11bmljYXRpb24gcHVibGljaXRhaXJlIGRlcyBhbm5vbmNldXJzIGTDqWRpw6lzIGV0IHBvdXIgZGVzIG1hcnF1ZXMgZGUgcHJlc3NlIGhhdXQgZGUgZ2FtbWUgZXQgbHV4ZSAoRWxsZSwgTnVtw6lyby47OykuIE1vbiBDViBlc3QgZW4gcGnDqGNlIGpvaW50ZS48YnI-PC9kaXY-PGRpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTNweCI-PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij48Yj5Qcm9qZXQgcHJvZmVzc2lvbm5lbDwvYj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTNweCI-SmUgdmV1eCBhbGxleiBwbHVzIGxvaW4gZGFucyBtb24gZMOpdmVsb3BwZW1lbnQgZGUgbWFycXVlcyBkZSBsdXhlIHNvaXQgYXUgbml2ZWF1IGNvbW1lcmNpYWwgc29pdCBhdSBuaXZlYXUgZGUgbGEgY29tbXVuaWNhdGlvbiBldCBjJ2VzdCBwb3VycXVvaSBqJ2FpIGludMOpZ3LDqSBTdXAgZGUgTHV4ZS4gSmUgdmlzZSBwb3VyIHNlcHRlbWJyZSBhdSBwbHVzIHRhcmQsIHVuZSBhY3Rpdml0w6kgY29tbWVyY2lhbGUgYXUgc2VpbiBkJ3VuZSBtYXJxdWUgZGUgbHV4ZS9tb2RlIG91IGVuIGFnZW5jZSAoZMOpdmVsb3BwZW1lbnQgb3UgY29uc2VpbCkuIFBvdXIgeSBwYXJ2ZW5pciBldCBlbnJpY2hpciBtYSB2YWxldXIgc3VyIGxlIG1hcmNow6ksIGplIGRvaXMgY29uc29saWRlciBjZSBxdWkgcydpbXBvc2UsIGxlIGRpZ2l0YWwhPC9kaXY-PGRpdiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTNweCI-PGI-TcOpbW9pcmUgLSByYXBwb3J0IFN1cCBkZSBMdXhlPC9iPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij5KJ2FpIGRvbmMgb3B0w6kgcG91ciB1biBzdWpldCBkZSBtw6ltb2lyZSBxdWkgcG9ydGUgc3VyIGxlIGx1eGUgZMOpZmnDqSBwYXIgbGUgZGlnaXRhbC4gSmUgdmV1eCBkw6ltb250cmVyIHF1ZSBsZSBsdXhlIGVzdCByw6l0aWNlbnQgZmFjZSBhdSBkaWdpdGFsLCBwb3VydGFudCBkw6ljaXNpZiBkYW5zIGwnYXBwb3J0IGRlIG5vdXZlYXV4IHNlcnZpY2VzIGV0IGRhbnMgbGUgZMOpcGxvaWVtZW50IGR1IGx1eGUuICZuYnNwOzwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij48dWw-PGxpIHN0eWxlPSJtYXJnaW4tbGVmdDoxNXB4Ij5NYWlzIG1vbiBzdWpldCBlc3QgdHJvcCBsYXJnZS4gSmUgbmUgcGV1eCB0cmFpdGVyIGRlIHRvdXMgbGVzIHNlcnZpY2VzIGFwcG9ydMOpcyBwYXIgbGUgZGlnaXRhbC4gSidow6lzaXRlIGVudHJlIGxhIGNvbW11bmljYXRpb24gZXQgbGEgc3RyYXTDqWdpZSBkJ29mZnJlIChwcm9kdWl0cykuIFF1ZWwgZXN0IGxlIHBsdXMgInV0aWxlIGQnYXByw6hzIHZvdXMiPzwvbGk-PC91bD48dWw-PGxpIHN0eWxlPSJtYXJnaW4tbGVmdDoxNXB4Ij5EZSBwbHVzIGplIG5lIHBldXggdHJhaXRlciBkdSBsdXhlIGVuIGfDqW7DqXJhbC4gSmUgZG9pcyBjaG9pc2lyIHVuIHNlZ21lbnQgZGUgY2VsdWktY2kgLiBKZSBzb3VoYWl0ZSBmb2NhbGlzZXIgc3VyIGxhIGpvYWlsbGVyaWUgcXVpIG1lIHNlbWJsZSBwbHVzIGTDqWZpw6llIHBhciBsZSBkaWdpdGFsIGNhciBjZSBzb250IGRldXggIm1hcmNow6lzIiBhIHByaW9yaSBhbnRpbm9taXF1ZXMuIENvbXByZW5kcmUgbGEgY29tcGxleGl0w6kgZHUgZHVvIEpvYWlsbGVyaWUgLyBkaWdpdGFsIG1lIHNlbWJsZSDDqWdhbGVtZW50IHBsdXMgZMOpdGVybWluYW50IHBvdXIgY29tcHJlbmRyZSBsZXMgZW5qZXV4IHBvc8OpcyBwYXIgbGUgZGlnaXRhbC4gUXUnZW4gcGVzbmV6LXZvdXM_PC9saT48L3VsPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij48YnI-PC9kaXY-PGRpdiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPkplIHZvdXMgcmVtZXJjaWUgcGFyIGF2YW5jZSBwb3VyIGwnYXR0ZW50aW9uIHF1ZSB2b3VzIGFjY29yZGVyZXogw6AgbW9uIHByb2pldCBldCBqZSB2b3VzIHByb3Bvc2UgZGUgY29udmVuaXIgZCd1biByZW5kZXogLSB2b3VzIHBvdXIgYXZhbmNlciBzdXIgY2VsdWktY2kuIEplIHN1aXMgZGlzcG9uaWJsZSBjZXR0ZSBzZW1haW5lIMOgIHZvdHJlIGNvbnZlbmFuY2UgZXQgdm91cyBwb3V2ZXogbeKAmWFwcGVsZXIgYXUgPGEgaHJlZj0idGVsOjA2JTIwNjIlMjAxOCUyMDQ3JTIwMDgiIHZhbHVlPSIrMzM2NjIxODQ3MDgiIHRhcmdldD0iX2JsYW5rIj4wNiA2MiAxOCA0NyAwODwvYT4uPC9kaXY-PGRpdiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTNweCI-QmllbiDDoCB2b3VzLDwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij48YnI-PC9kaXY-PGRpdiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPkFybMOobmUgQmVudG9saWxhPC9kaXY-PGRpdiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LXNpemU6MTNweCI-PGJyPjwvZGl2PjwvZGl2PjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj4tLS0tLS0tLS0tIE1lc3NhZ2UgdHJhbnNmw6lyw6kgLS0tLS0tLS0tLTxicj5EZSZuYnNwOzogPGIgY2xhc3M9ImdtYWlsX3NlbmRlcm5hbWUiPkdoaXNsYWluZSBMZSBHb3VndWVjPC9iPiA8c3BhbiBkaXI9Imx0ciI-Jmx0OzxhIGhyZWY9Im1haWx0bzpnbGVnb3VndWVjQGVkY3BhcmlzLmVkdSIgdGFyZ2V0PSJfYmxhbmsiPmdsZWdvdWd1ZWNAZWRjcGFyaXMuZWR1PC9hPiZndDs8L3NwYW4-PGJyPkRhdGUmbmJzcDs6IDE5IGTDqWNlbWJyZSAyMDE0IDE5OjQ2PGJyPk9iamV0Jm5ic3A7OiBSRTogU3VqZXQgZGUgbcOpbW9pcmUgdW5pcXVlIEFybMOobmUgZXQgImV4ZXJjaWNlIiA6IGplIHZldXggZMOpbW9udHJlciBxdWU8YnI-w4AmbmJzcDs6IEFybGVuZSBCZW50b2xpbGEgJmx0OzxhIGhyZWY9Im1haWx0bzphcmxlbmViZW50b2xpbGFAZ21haWwuY29tIiB0YXJnZXQ9Il9ibGFuayI-YXJsZW5lYmVudG9saWxhQGdtYWlsLmNvbTwvYT4mZ3Q7PGJyPkNjIDogdGRlbGFyaXZpZXJlICZsdDs8YSBocmVmPSJtYWlsdG86dGRlbGFyaXZpZXJlQGVkY3BhcmlzLmVkdSIgdGFyZ2V0PSJfYmxhbmsiPnRkZWxhcml2aWVyZUBlZGNwYXJpcy5lZHU8L2E-Jmd0Ozxicj48YnI-PGJyPjxkaXYgbGFuZz0iRlIiIGxpbms9ImJsdWUiIHZsaW5rPSJwdXJwbGUiPjxkaXY-PHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMXB0O2ZvbnQtZmFtaWx5OidNYWlhbmRyYSBHRCcsc2Fucy1zZXJpZjtjb2xvcjpibGFjayI-Q2jDqHJlIEFybMOobmUsPHU-PC91Pjx1PjwvdT48L3NwYW4-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTFwdDtmb250LWZhbWlseTonTWFpYW5kcmEgR0QnLHNhbnMtc2VyaWY7Y29sb3I6YmxhY2siPk1lcmNpIHBvdXIgY2VzIGluZm9ybWF0aW9ucy4gPHU-PC91Pjx1PjwvdT48L3NwYW4-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTFwdDtmb250LWZhbWlseTonTWFpYW5kcmEgR0QnLHNhbnMtc2VyaWY7Y29sb3I6YmxhY2siPk5vdXMgcHJlbm9ucyBub3RlIGRlIHZvdHJlIG5vdXZlYXUgc3VqZXQgZGUgbcOpbW9pcmUmbmJzcDs6ICZuYnNwOzwvc3Bhbj48Yj48dT48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExcHQ7Zm9udC1mYW1pbHk6J01haWFuZHJhIEdEJyxzYW5zLXNlcmlmO2NvbG9yOmJsYWNrIj5MZXMgZMOpZmlzPGEgbmFtZT0iMTRhYmEwOTg2OTQwNTdiNl8xNGE2M2RlZjk5ZmU2NzJhX19Hb0JhY2siPjwvYT4gZHUgZGlnaXRhbCBkYW5zIGxlcyBzdHJhdMOpZ2llcyBkZSBkw6l2ZWxvcHBlbWVudCBkdSBsdXhlIGVuIG1hdGnDqHJlJm5ic3A7IGTigJlvZmZyZSwgZGUgc2VydmljZXMsIGRlIGRpc3RyaWJ1dGlvbiBldCBkZSBjb21tdW5pY2F0aW9uPC9zcGFuPjwvdT48L2I-PHU-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMXB0O2ZvbnQtZmFtaWx5OidNYWlhbmRyYSBHRCcsc2Fucy1zZXJpZjtjb2xvcjpibGFjayI-PHU-PC91Pjx1PjwvdT48L3NwYW4-PC91PjwvcD48cCBjbGFzcz0iTXNvTm9ybWFsIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExcHQ7Zm9udC1mYW1pbHk6J01haWFuZHJhIEdEJyxzYW5zLXNlcmlmO2NvbG9yOmJsYWNrIj5Ob3VzIGVuIHZhbGlkb25zIGxlIHRow6htZSBtYWlzIHBlbnNvbnMgdG91dGVmb2lzIHF14oCZaWwgZXN0IGVuY29yZSB0cm9wIGxhcmdlIGVuIHRlcm1lIGRlIHDDqXJpbcOodHJlIGTigJlhbmFseXNlICsgamUgcGVuc2UgcXVlIHZvdXMgZGV2ZXogdm91cyBjb25jZW50cmVyIHN1ciB1biBzZWN0ZXVyIGV0IG5vbiBsZSBsdXhlIGVuIGfDqW7DqXJhbC48dT48L3U-PHU-PC91Pjwvc3Bhbj48L3A-PHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMXB0O2ZvbnQtZmFtaWx5OidNYWlhbmRyYSBHRCcsc2Fucy1zZXJpZjtjb2xvcjpibGFjayI-Vm91cyBkZXZleiByZW5jb250cmVyIGRlcyBwcm9mZXNzaW9ubmVscyByYXBpZGVtZW50IHF1aSB2b3VzIGFpZGVyb250IMOgIGTDqWZpbmlyIHVuZSBwcm9ibMOpbWF0aXF1ZSBwbHVzIHByw6ljaXNlLiBNZXJjaSBkZSBjb250YWN0ZXIgTcOpbGFuaWUgcXVpIHZvdXMgZG9ubmVyYSBsZXMgY29vcmRvbm7DqWVzIGRlIE1vbnNpZXVyIEfDqXJhbGQgRVNQQVJERUxMSUVSLCBEaXJlY3RldXIgQXNzb2Npw6kgZGUgbGEgc29jacOpdMOpIEVOT1JBIENvbnN1bHRpbmcgKyBjb250YWN0ZXogZGUgbm90cmUgcGFydCBEYXZpZCBBbGV4YW5kcmUgS2xpbmdiZWlsLCBDRU8gJmFtcDsmbmJzcDtDby1Gb3VuZGVyIHwgPGEgaHJlZj0iaHR0cDovL0R5bWFudC5jb20iIHRhcmdldD0iX2JsYW5rIj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7dGV4dC1kZWNvcmF0aW9uOm5vbmUiPkR5bWFudC5jb208L3NwYW4-PC9hPiAtIDwvc3Bhbj48c3BhbiBzdHlsZT0iZm9udC1mYW1pbHk6J01haWFuZHJhIEdEJyxzYW5zLXNlcmlmO2NvbG9yOmJsYWNrIj4mbmJzcDs8YSBocmVmPSJtYWlsdG86ZGFrQGR5bWFudC5jb20iIHRhcmdldD0iX2JsYW5rIj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2siPmRha0BkeW1hbnQuY29tPC9zcGFuPjwvYT4gPC9zcGFuPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTFwdDtmb250LWZhbWlseTonTWFpYW5kcmEgR0QnLHNhbnMtc2VyaWY7Y29sb3I6YmxhY2siPisgY28gZm9uZGF0ZXVyIGR1IG1hZ2F6aW5lIGRpZ2l0YWwgV2ViICZhbXA7IEx1eGUgPGEgaHJlZj0iaHR0cDovL3d3dy53ZWJhbmRsdXhlLmNvbS9hLXByb3Bvcy8iIHRhcmdldD0iX2JsYW5rIj5odHRwOi8vd3d3LndlYmFuZGx1eGUuY29tL2EtcHJvcG9zLzwvYT48dT48L3U-PHU-PC91Pjwvc3Bhbj48L3A-PHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMXB0O2ZvbnQtZmFtaWx5OidNYWlhbmRyYSBHRCcsc2Fucy1zZXJpZjtjb2xvcjpibGFjayI-QmllbiDDoCB2b3VzLDx1PjwvdT48dT48L3U-PC9zcGFuPjwvcD48cCBjbGFzcz0iTXNvTm9ybWFsIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjExcHQ7Zm9udC1mYW1pbHk6J01haWFuZHJhIEdEJyxzYW5zLXNlcmlmO2NvbG9yOmJsYWNrIj5HbEc8dT48L3U-PHU-PC91Pjwvc3Bhbj48L3A-PHAgY2xhc3M9Ik1zb05vcm1hbCI-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMXB0O2ZvbnQtZmFtaWx5OkNhbGlicmksc2Fucy1zZXJpZjtjb2xvcjpyZ2IoMzEsNzMsMTI1KSI-PHU-PC91PiZuYnNwOzx1PjwvdT48L3NwYW4-PC9wPjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTFwdDtmb250LWZhbWlseTpDYWxpYnJpLHNhbnMtc2VyaWY7Y29sb3I6cmdiKDMxLDczLDEyNSkiPjx1PjwvdT4mbmJzcDs8dT48L3U-PC9zcGFuPjwvcD48ZGl2IHN0eWxlPSJib3JkZXItc3R5bGU6c29saWQgbm9uZSBub25lO2JvcmRlci10b3AtY29sb3I6cmdiKDE4MSwxOTYsMjIzKTtib3JkZXItdG9wLXdpZHRoOjFwdDtwYWRkaW5nOjNwdCAwY20gMGNtIj48cCBjbGFzcz0iTXNvTm9ybWFsIj48Yj48c3BhbiBzdHlsZT0iZm9udC1zaXplOjEwcHQ7Zm9udC1mYW1pbHk6VGFob21hLHNhbnMtc2VyaWYiPkRlJm5ic3A7Ojwvc3Bhbj48L2I-PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxMHB0O2ZvbnQtZmFtaWx5OlRhaG9tYSxzYW5zLXNlcmlmIj4gQXJsZW5lIEJlbnRvbGlsYSBbbWFpbHRvOjxhIGhyZWY9Im1haWx0bzphcmxlbmViZW50b2xpbGFAZ21haWwuY29tIiB0YXJnZXQ9Il9ibGFuayI-YXJsZW5lYmVudG9saWxhQGdtYWlsLmNvbTwvYT5dIDxicj48Yj5FbnZvecOpJm5ic3A7OjwvYj4gdmVuZHJlZGkgMTkgZMOpY2VtYnJlIDIwMTQgMTk6MTg8YnI-PGI-w4AmbmJzcDs6PC9iPiA8YSBocmVmPSJtYWlsdG86Z2hpc2xhaW5lLmxlZ291Z3VlY0BlZGNwYXJpcy5lZHUiIHRhcmdldD0iX2JsYW5rIj5naGlzbGFpbmUubGVnb3VndWVjQGVkY3BhcmlzLmVkdTwvYT48YnI-PGI-Q2MmbmJzcDs6PC9iPiA8YSBocmVmPSJtYWlsdG86dGhpYmF1dC5kZWxhcml2aWVyZUBlZGNwYXJpcy5lZHUiIHRhcmdldD0iX2JsYW5rIj50aGliYXV0LmRlbGFyaXZpZXJlQGVkY3BhcmlzLmVkdTwvYT48YnI-PGI-T2JqZXQmbmJzcDs6PC9iPiBTdWpldCBkZSBtw6ltb2lyZSB1bmlxdWUgQXJsw6huZSBldCAiZXhlcmNpY2UiIDogamUgdmV1eCBkw6ltb250cmVyIHF1ZTx1PjwvdT48dT48L3U-PC9zcGFuPjwvcD48L2Rpdj48ZGl2PjxkaXY-PHAgY2xhc3M9Ik1zb05vcm1hbCI-PHU-PC91PiZuYnNwOzx1PjwvdT48L3A-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIj5DaGVycyB0b3VzPHU-PC91Pjx1PjwvdT48L3A-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIj48dT48L3U-Jm5ic3A7PHU-PC91PjwvcD48L2Rpdj48ZGl2PjxwIGNsYXNzPSJNc29Ob3JtYWwiPkonYWkgcmVwZW5zw6kgbW9uIHN1amV0IGRlIG3DqW1vaXJlIGVuIGZvbmN0aW9uIGRlIG1lcyBnb8O7dHMsIGRlIG1lcyBjb21ww6l0ZW5jZXMgZXQgZGUgbWVzIG9iamVjdGlmcyBwcm9mZXNzaW9ubmVscy4mbmJzcDs8dT48L3U-PHU-PC91PjwvcD48L2Rpdj48ZGl2PjxwIGNsYXNzPSJNc29Ob3JtYWwiPjx1PjwvdT4mbmJzcDs8dT48L3U-PC9wPjwvZGl2PjxkaXY-PHAgY2xhc3M9Ik1zb05vcm1hbCI-SmUgcGFycyBkb25jIHN1ciBsYSB2ZXJzaW9uIHF1ZSB2b3VzIGTDqWNvdXZyaXJleiBlbiBwacOoY2Ugam9pbnRlIGV0IHF1aSBwb3J0ZSBzdXIgbGVzIGVuamV1eCBkdSBkaWdpdGFsIGRhbnMgbGVzIHN0cmF0w6lnaWVzIGRlIGTDqXZlbG9wcGVtZW50IGR1IGx1eGUgZW4gbWF0acOocmUgZCdvZmZyZSwgZGUgc2VydmljZSwgZGUgZGlzdHJpYnV0aW9uIGV0IGRlIGNvbW11bmljYXRpb24uJm5ic3A7PHU-PC91Pjx1PjwvdT48L3A-PC9kaXY-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIj5KZSBtZSBzdWlzIMOpZ2FsZW1lbnQgcHLDqnTDqWUgYXUgbsOpY2Vzc2FpcmUgZXhlcmNpY2UgZHUgOiAmbmJzcDsiamUgdmV1eCBkw6ltb250cmVyIHF1ZSIuPHU-PC91Pjx1PjwvdT48L3A-PC9kaXY-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIj48dT48L3U-Jm5ic3A7PHU-PC91PjwvcD48L2Rpdj48ZGl2PjxwIGNsYXNzPSJNc29Ob3JtYWwiPkplIHJlc3RlIMOgIHZvdHJlIMOpY291dGUgcG91ciBmaW5hbGlzZXIgY2V0dGUgZmljaGUgcMOpZGFnb2dpcXVlIGV0IHZhbGlkZXIgbW9uIHN1amV0IGRlIG3DqW1vaXJlIGF2YW50IGxhIHJlbnRyw6llIGRlIGphbnZpZXIgc2kgY2VsYSB2b3VzIGVzdCBwb3NzaWJsZS4gSmUgdmFpcyBkJ29yZXMgZXQgZMOpasOgIGNvbW1lbmNlciBtZXMgcmVjaGVyY2hlcyBldCBmYWlyZSB1bmUgw6liYXVjaGUgZGUgcGxhbiBwZW5kYW50IGNlcyB2YWNhbmNlcy4mbmJzcDs8dT48L3U-PHU-PC91PjwvcD48L2Rpdj48ZGl2PjxwIGNsYXNzPSJNc29Ob3JtYWwiPjx1PjwvdT4mbmJzcDs8dT48L3U-PC9wPjwvZGl2PjxkaXY-PHAgY2xhc3M9Ik1zb05vcm1hbCI-SmUgdm91cyBzb3VoYWl0ZSBkZSB0csOocyBib25uZXMgZsOqdGVzLjx1PjwvdT48dT48L3U-PC9wPjwvZGl2PjxkaXY-PHAgY2xhc3M9Ik1zb05vcm1hbCI-PHU-PC91PiZuYnNwOzx1PjwvdT48L3A-PC9kaXY-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIj5CaWVuIMOgIHZvdXMsPHU-PC91Pjx1PjwvdT48L3A-PC9kaXY-PGRpdj48cCBjbGFzcz0iTXNvTm9ybWFsIj48dT48L3U-Jm5ic3A7PHU-PC91PjwvcD48L2Rpdj48ZGl2PjxwIGNsYXNzPSJNc29Ob3JtYWwiPkFybMOobmU8dT48L3U-PHU-PC91PjwvcD48L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj4NCjxicj48YnI-PC9kaXY-PC9kaXY-PGJyPjwvZGl2Pg0KPC9ibG9ja3F1b3RlPjwvZGl2Pjxicj48L2Rpdj4NCjwvZGl2PjwvYmxvY2txdW90ZT48L2JvZHk-PC9odG1sPg=="}}]}, :sizeEstimate=>26532}.merge(params) 62 | end 63 | 64 | 65 | def test_to_reply_message(params = {}) 66 | {:id=>"14aecf708ee65122", :threadId=>"14aecf708ee65122", :labelIds=>["INBOX", "IMPORTANT", "CATEGORY_PERSONAL", "UNREAD"], :snippet=>"coucou Julien Hobeika | Co-Founder & CEO Email: julien@juliedesk.com Tel: +33 6 63 33 17 55 Julie", :historyId=>"232755", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"Delivered-To", :value=>"julie@juliedesk.com"}, {:name=>"Received", :value=>"by 10.64.89.39 with SMTP id bl7csp1888330ieb; Thu, 15 Jan 2015 01:40:21 -0800 (PST)"}, {:name=>"X-Received", :value=>"by 10.194.2.75 with SMTP id 11mr16697173wjs.78.1421314820755; Thu, 15 Jan 2015 01:40:20 -0800 (PST)"}, {:name=>"Return-Path", :value=>""}, {:name=>"Received", :value=>"from mail-wg0-f52.google.com (mail-wg0-f52.google.com. [74.125.82.52]) by mx.google.com with ESMTPS id cz7si1861265wjc.17.2015.01.15.01.40.19 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Thu, 15 Jan 2015 01:40:20 -0800 (PST)"}, {:name=>"Received-SPF", :value=>"pass (google.com: domain of julien@wepopp.com designates 74.125.82.52 as permitted sender) client-ip=74.125.82.52;"}, {:name=>"Authentication-Results", :value=>"mx.google.com; spf=pass (google.com: domain of julien@wepopp.com designates 74.125.82.52 as permitted sender) smtp.mail=julien@wepopp.com"}, {:name=>"Received", :value=>"by mail-wg0-f52.google.com with SMTP id x12so13734359wgg.11 for ; Thu, 15 Jan 2015 01:40:19 -0800 (PST)"}, {:name=>"X-Google-DKIM-Signature", :value=>"v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to:cc :content-type; bh=VX+NTBD3y6zC7y2eoK6dXfXslqvi6t2cSoTCjb3ydPE=; b=GJnt5rk9Ck0uf9drEbY8tmxHqEosl3+89TyqId0TMttNgmjhV+rTX5tGe1N1wbPS0o ZMl5zDe/A5GGd7PHa8ykZZwNv5GQn0cjMQIMs+nMyUTUCUYeioWirF3frDJ8siV7+EXF s+l5+8Ul3suVPTcg5+zoNGgTJu3mWBC4E+1KMbVLYNOZtOd5PEMJPCZuTu2/k8Vw0Iw6 8JG02sSAzWRxNJnUoCRrShTV7C+eNRehvI7utHGMrobP1chWb2qZgLvbwGnpo/eBceya XWo/bpd9OcAYhb0spf0M2pXJ6TL/fzYbplFj0Lj8sXfRphQJivUjUVj4F02Tty2HpQ+q 4kQw=="}, {:name=>"X-Gm-Message-State", :value=>"ALoCoQk6eM56sO2fhoPw1I+al4RvoL3xd/Mzo7nmPK4WlB/OUaOD0isPUduI2MUrEdZgQ8tty/AH"}, {:name=>"X-Received", :value=>"by 10.180.126.99 with SMTP id mx3mr17257983wib.66.1421314819809; Thu, 15 Jan 2015 01:40:19 -0800 (PST)"}, {:name=>"MIME-Version", :value=>"1.0"}, {:name=>"Received", :value=>"by 10.180.8.228 with HTTP; Thu, 15 Jan 2015 01:39:59 -0800 (PST)"}, {:name=>"From", :value=>"Julien Hobeika "}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 10:39:59 +0100"}, {:name=>"Message-ID", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"To", :value=>"Julie Desk , Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Nicolas Marlier "}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=e89a8f8389d1f2fbe9050cada4ac"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>144, :data=>"Y291Y291DQoNCg0KKkp1bGllbiBIb2JlaWthIHwgQ28tRm91bmRlciAmIENFTypFbWFpbDoganVsaWVuQGp1bGllZGVzay5jb20NClRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCg0KKkp1bGllIERlc2sqDQp3d3cuanVsaWVkZXNrLmNvbQ0KDQoNCuGQpw0K"}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>742, :data=>"PGRpdiBkaXI9Imx0ciI-Y291Y291PGJyIGNsZWFyPSJhbGwiPjxkaXY-PGRpdiBjbGFzcz0iZ21haWxfc2lnbmF0dXJlIj48ZGl2IGRpcj0ibHRyIj48cD48Yj5KdWxpZW4gSG9iZWlrYSB8IENvLUZvdW5kZXIgJmFtcDsgQ0VPPGJyPjwvYj5FbWFpbDrCoDxhIGhyZWY9Im1haWx0bzpqdWxpZW5AanVsaWVkZXNrLmNvbSIgdGFyZ2V0PSJfYmxhbmsiPmp1bGllbkBqdWxpZWRlc2suY29tPC9hPjxicj5UZWw6ICszMyA2IDYzIDMzIDE3IDU1PC9wPjxwPjxiPkp1bGllIERlc2s8L2I-PGI-PGJyPjwvYj48YSBocmVmPSJodHRwOi8vd3d3Lmp1bGllZGVzay5jb20vIiBzdHlsZT0iY29sb3I6cmdiKDE3LDg1LDIwNCk7Zm9udC1mYW1pbHk6SGVsdmV0aWNhIiB0YXJnZXQ9Il9ibGFuayI-d3d3Lmp1bGllZGVzay5jb208L2E-PGI-PGJyPjwvYj48L3A-PHA-PGJyPjwvcD48L2Rpdj48L2Rpdj48L2Rpdj4NCjxkaXYgaHNwYWNlPSJzdHJlYWstcHQtbWFyayIgc3R5bGU9Im1heC1oZWlnaHQ6MXB4Ij48aW1nIHN0eWxlPSJ3aWR0aDowcHg7IG1heC1oZWlnaHQ6MHB4OyIgc3JjPSJodHRwczovL21haWxmb29nYWUuYXBwc3BvdC5jb20vdD9zZW5kZXI9YWFuVnNhV1Z1UUhkbGNHOXdjQzVqYjIwJTNEJmFtcDt0eXBlPXplcm9jb250ZW50JmFtcDtndWlkPTBiMWRiODRkLTFmMTctNDE1MS05ZmE5LTUxMWNiYTg0ODAyMiI-PGZvbnQgY29sb3I9IiNmZmZmZmYiIHNpemU9IjEiPuGQpzwvZm9udD48L2Rpdj48L2Rpdj4NCg=="}}]}, :sizeEstimate=>3710} 67 | end 68 | 69 | def test_to_reply_message2(params = {}) #without delivered_to in headers 70 | {:id=>"14aecf708ee65122", :threadId=>"14aecf708ee65122", :labelIds=>["INBOX", "IMPORTANT", "CATEGORY_PERSONAL", "UNREAD"], :snippet=>"coucou Julien Hobeika | Co-Founder & CEO Email: julien@juliedesk.com Tel: +33 6 63 33 17 55 Julie", :historyId=>"232755", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"Received", :value=>"by 10.64.89.39 with SMTP id bl7csp1888330ieb; Thu, 15 Jan 2015 01:40:21 -0800 (PST)"}, {:name=>"X-Received", :value=>"by 10.194.2.75 with SMTP id 11mr16697173wjs.78.1421314820755; Thu, 15 Jan 2015 01:40:20 -0800 (PST)"}, {:name=>"Return-Path", :value=>""}, {:name=>"Received", :value=>"from mail-wg0-f52.google.com (mail-wg0-f52.google.com. [74.125.82.52]) by mx.google.com with ESMTPS id cz7si1861265wjc.17.2015.01.15.01.40.19 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Thu, 15 Jan 2015 01:40:20 -0800 (PST)"}, {:name=>"Received-SPF", :value=>"pass (google.com: domain of julien@wepopp.com designates 74.125.82.52 as permitted sender) client-ip=74.125.82.52;"}, {:name=>"Authentication-Results", :value=>"mx.google.com; spf=pass (google.com: domain of julien@wepopp.com designates 74.125.82.52 as permitted sender) smtp.mail=julien@wepopp.com"}, {:name=>"Received", :value=>"by mail-wg0-f52.google.com with SMTP id x12so13734359wgg.11 for ; Thu, 15 Jan 2015 01:40:19 -0800 (PST)"}, {:name=>"X-Google-DKIM-Signature", :value=>"v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to:cc :content-type; bh=VX+NTBD3y6zC7y2eoK6dXfXslqvi6t2cSoTCjb3ydPE=; b=GJnt5rk9Ck0uf9drEbY8tmxHqEosl3+89TyqId0TMttNgmjhV+rTX5tGe1N1wbPS0o ZMl5zDe/A5GGd7PHa8ykZZwNv5GQn0cjMQIMs+nMyUTUCUYeioWirF3frDJ8siV7+EXF s+l5+8Ul3suVPTcg5+zoNGgTJu3mWBC4E+1KMbVLYNOZtOd5PEMJPCZuTu2/k8Vw0Iw6 8JG02sSAzWRxNJnUoCRrShTV7C+eNRehvI7utHGMrobP1chWb2qZgLvbwGnpo/eBceya XWo/bpd9OcAYhb0spf0M2pXJ6TL/fzYbplFj0Lj8sXfRphQJivUjUVj4F02Tty2HpQ+q 4kQw=="}, {:name=>"X-Gm-Message-State", :value=>"ALoCoQk6eM56sO2fhoPw1I+al4RvoL3xd/Mzo7nmPK4WlB/OUaOD0isPUduI2MUrEdZgQ8tty/AH"}, {:name=>"X-Received", :value=>"by 10.180.126.99 with SMTP id mx3mr17257983wib.66.1421314819809; Thu, 15 Jan 2015 01:40:19 -0800 (PST)"}, {:name=>"MIME-Version", :value=>"1.0"}, {:name=>"Received", :value=>"by 10.180.8.228 with HTTP; Thu, 15 Jan 2015 01:39:59 -0800 (PST)"}, {:name=>"From", :value=>"Julien Hobeika "}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 10:39:59 +0100"}, {:name=>"Message-ID", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"To", :value=>"Julie Desk , Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Nicolas Marlier "}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=e89a8f8389d1f2fbe9050cada4ac"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>144, :data=>"Y291Y291DQoNCg0KKkp1bGllbiBIb2JlaWthIHwgQ28tRm91bmRlciAmIENFTypFbWFpbDoganVsaWVuQGp1bGllZGVzay5jb20NClRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCg0KKkp1bGllIERlc2sqDQp3d3cuanVsaWVkZXNrLmNvbQ0KDQoNCuGQpw0K"}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>742, :data=>"PGRpdiBkaXI9Imx0ciI-Y291Y291PGJyIGNsZWFyPSJhbGwiPjxkaXY-PGRpdiBjbGFzcz0iZ21haWxfc2lnbmF0dXJlIj48ZGl2IGRpcj0ibHRyIj48cD48Yj5KdWxpZW4gSG9iZWlrYSB8IENvLUZvdW5kZXIgJmFtcDsgQ0VPPGJyPjwvYj5FbWFpbDrCoDxhIGhyZWY9Im1haWx0bzpqdWxpZW5AanVsaWVkZXNrLmNvbSIgdGFyZ2V0PSJfYmxhbmsiPmp1bGllbkBqdWxpZWRlc2suY29tPC9hPjxicj5UZWw6ICszMyA2IDYzIDMzIDE3IDU1PC9wPjxwPjxiPkp1bGllIERlc2s8L2I-PGI-PGJyPjwvYj48YSBocmVmPSJodHRwOi8vd3d3Lmp1bGllZGVzay5jb20vIiBzdHlsZT0iY29sb3I6cmdiKDE3LDg1LDIwNCk7Zm9udC1mYW1pbHk6SGVsdmV0aWNhIiB0YXJnZXQ9Il9ibGFuayI-d3d3Lmp1bGllZGVzay5jb208L2E-PGI-PGJyPjwvYj48L3A-PHA-PGJyPjwvcD48L2Rpdj48L2Rpdj48L2Rpdj4NCjxkaXYgaHNwYWNlPSJzdHJlYWstcHQtbWFyayIgc3R5bGU9Im1heC1oZWlnaHQ6MXB4Ij48aW1nIHN0eWxlPSJ3aWR0aDowcHg7IG1heC1oZWlnaHQ6MHB4OyIgc3JjPSJodHRwczovL21haWxmb29nYWUuYXBwc3BvdC5jb20vdD9zZW5kZXI9YWFuVnNhV1Z1UUhkbGNHOXdjQzVqYjIwJTNEJmFtcDt0eXBlPXplcm9jb250ZW50JmFtcDtndWlkPTBiMWRiODRkLTFmMTctNDE1MS05ZmE5LTUxMWNiYTg0ODAyMiI-PGZvbnQgY29sb3I9IiNmZmZmZmYiIHNpemU9IjEiPuGQpzwvZm9udD48L2Rpdj48L2Rpdj4NCg=="}}]}, :sizeEstimate=>3710} 71 | end 72 | 73 | def test_reply_message(params = {}) 74 | {body: "test"} 75 | end 76 | 77 | def test_forward_message 78 | {to: "julien@wepopp.com", bbc: "julien.hobeika@gmail.com", cc: "julien@juliedesk.com, nicolas@juliedesk.com", subject: "cool subject", body: "test"} 79 | end 80 | 81 | def test_forward_message_with_html 82 | {to: "julien@wepopp.com", bbc: "julien.hobeika@gmail.com", cc: "julien@juliedesk.com, nicolas@juliedesk.com", subject: "cool subject", text: "test", html: "test"} 83 | end 84 | 85 | def test_forwarded_message 86 | {:id=>"14aef32bd53a74bb", :threadId=>"14aecf708ee65122", :labelIds=>["SENT"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: >coucou >", :historyId=>"234207", :payload=>{:partId=>"", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 12:04:47 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 12:04:47 -0800"}, {:name=>"To", :value=>"julien@wepopp.com"}, {:name=>"Cc", :value=>"julien@juliedesk.com, nicolas@juliedesk.com"}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"cool subject"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, :sizeEstimate=>948} 87 | end 88 | 89 | 90 | def test_forwarded_message_with_html 91 | {:id=>"14aef33bfcd19656", :threadId=>"14aecf708ee65122", :labelIds=>["SENT"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: coucou Julien", :historyId=>"234237", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 12:05:53 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 12:05:53 -0800"}, {:name=>"To", :value=>"julien@wepopp.com"}, {:name=>"Cc", :value=>"julien@juliedesk.com, nicolas@juliedesk.com"}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"cool subject"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=\"--==_mimepart_54b81da0a179_d3733fe0444541d8958c\"; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"7bit"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>1005, :data=>"PGRpdj48Yj50ZXN0PC9iPjwvZGl2Pg0KPGJyPjxicj48ZGl2IGNsYXNzPSJnbWFpbF9xdW90ZSI-IFRodSwgMTUgSmFuIDIwMTUgMTA6Mzk6NTkgKzAxMDAgSnVsaWVuIEhvYmVpa2EgJmx0O2p1bGllbkBqdWxpZWRlc2suY29tJmd0Ozo8YnI-PGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0ibWFyZ2luOjAgMCAwIC44ZXg7Ym9yZGVyLWxlZnQ6MXB4ICNjY2Mgc29saWQ7cGFkZGluZy1sZWZ0OjFleCI-PGRpdiBkaXI9Imx0ciI-Y291Y291PGJyIGNsZWFyPSJhbGwiPjxkaXY-PGRpdiBjbGFzcz0iZ21haWxfc2lnbmF0dXJlIj48ZGl2IGRpcj0ibHRyIj48cD48Yj5KdWxpZW4gSG9iZWlrYSB8IENvLUZvdW5kZXIgJmFtcDsgQ0VPPGJyPjwvYj5FbWFpbDrCoDxhIGhyZWY9Im1haWx0bzpqdWxpZW5AanVsaWVkZXNrLmNvbSIgdGFyZ2V0PSJfYmxhbmsiPmp1bGllbkBqdWxpZWRlc2suY29tPC9hPjxicj5UZWw6ICszMyA2IDYzIDMzIDE3IDU1PC9wPjxwPjxiPkp1bGllIERlc2s8L2I-PGI-PGJyPjwvYj48YSBocmVmPSJodHRwOi8vd3d3Lmp1bGllZGVzay5jb20vIiBzdHlsZT0iY29sb3I6cmdiKDE3LDg1LDIwNCk7Zm9udC1mYW1pbHk6SGVsdmV0aWNhIiB0YXJnZXQ9Il9ibGFuayI-d3d3Lmp1bGllZGVzay5jb208L2E-PGI-PGJyPjwvYj48L3A-PHA-PGJyPjwvcD48L2Rpdj48L2Rpdj48L2Rpdj4NCjxkaXYgaHNwYWNlPSJzdHJlYWstcHQtbWFyayIgc3R5bGU9Im1heC1oZWlnaHQ6MXB4Ij48aW1nIHN0eWxlPSJ3aWR0aDowcHg7IG1heC1oZWlnaHQ6MHB4OyIgc3JjPSJodHRwczovL21haWxmb29nYWUuYXBwc3BvdC5jb20vdD9zZW5kZXI9YWFuVnNhV1Z1UUhkbGNHOXdjQzVqYjIwJTNEJmFtcDt0eXBlPXplcm9jb250ZW50JmFtcDtndWlkPTBiMWRiODRkLTFmMTctNDE1MS05ZmE5LTUxMWNiYTg0ODAyMiI-PGZvbnQgY29sb3I9IiNmZmZmZmYiIHNpemU9IjEiPuGQpzwvZm9udD48L2Rpdj48L2Rpdj4NCjwvYmxvY2txdW90ZT48L2Rpdj48YnI-"}}]}, :sizeEstimate=>2448} 92 | end 93 | 94 | def test_reply_message_with_html(params = {}) 95 | {html: "test", text: "test"} 96 | end 97 | 98 | def test_replied_message(params = {}) 99 | {:id=>"14aecf9e31d945ba", :threadId=>"14aecf708ee65122", :labelIds=>["SENT", "INBOX", "UNREAD"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: >coucou >", :historyId=>"232799", :payload=>{:partId=>"", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"To", :value=>"Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Julien Hobeika , Nicolas Marlier "}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, :sizeEstimate=>1089} 100 | end 101 | 102 | 103 | def test_replied_message_with_html(params = {}) 104 | {:id=>"14aef0d08d39b4df", :threadId=>"14aecf708ee65122", :labelIds=>["SENT", "INBOX", "UNREAD"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: coucou Julien", :historyId=>"234054", :payload=>{:mimeType=>"multipart/alternative", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 11:23:36 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 11:23:36 -0800"}, {:name=>"To", :value=>"Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Julien Hobeika , Nicolas Marlier "}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"multipart/alternative; boundary=\"--==_mimepart_54b813b749eaa_d0963fc7bc45c1d0581fa\"; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"7bit"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>0}, :parts=>[{:partId=>"0", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, {:partId=>"1", :mimeType=>"text/html", :filename=>"", :headers=>[{:name=>"Content-Type", :value=>"text/html; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}], :body=>{:size=>1005, :data=>"PGRpdj48Yj50ZXN0PC9iPjwvZGl2Pg0KPGJyPjxicj48ZGl2IGNsYXNzPSJnbWFpbF9xdW90ZSI-IFRodSwgMTUgSmFuIDIwMTUgMTA6Mzk6NTkgKzAxMDAgSnVsaWVuIEhvYmVpa2EgJmx0O2p1bGllbkBqdWxpZWRlc2suY29tJmd0Ozo8YnI-PGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0ibWFyZ2luOjAgMCAwIC44ZXg7Ym9yZGVyLWxlZnQ6MXB4ICNjY2Mgc29saWQ7cGFkZGluZy1sZWZ0OjFleCI-PGRpdiBkaXI9Imx0ciI-Y291Y291PGJyIGNsZWFyPSJhbGwiPjxkaXY-PGRpdiBjbGFzcz0iZ21haWxfc2lnbmF0dXJlIj48ZGl2IGRpcj0ibHRyIj48cD48Yj5KdWxpZW4gSG9iZWlrYSB8IENvLUZvdW5kZXIgJmFtcDsgQ0VPPGJyPjwvYj5FbWFpbDrCoDxhIGhyZWY9Im1haWx0bzpqdWxpZW5AanVsaWVkZXNrLmNvbSIgdGFyZ2V0PSJfYmxhbmsiPmp1bGllbkBqdWxpZWRlc2suY29tPC9hPjxicj5UZWw6ICszMyA2IDYzIDMzIDE3IDU1PC9wPjxwPjxiPkp1bGllIERlc2s8L2I-PGI-PGJyPjwvYj48YSBocmVmPSJodHRwOi8vd3d3Lmp1bGllZGVzay5jb20vIiBzdHlsZT0iY29sb3I6cmdiKDE3LDg1LDIwNCk7Zm9udC1mYW1pbHk6SGVsdmV0aWNhIiB0YXJnZXQ9Il9ibGFuayI-d3d3Lmp1bGllZGVzay5jb208L2E-PGI-PGJyPjwvYj48L3A-PHA-PGJyPjwvcD48L2Rpdj48L2Rpdj48L2Rpdj4NCjxkaXYgaHNwYWNlPSJzdHJlYWstcHQtbWFyayIgc3R5bGU9Im1heC1oZWlnaHQ6MXB4Ij48aW1nIHN0eWxlPSJ3aWR0aDowcHg7IG1heC1oZWlnaHQ6MHB4OyIgc3JjPSJodHRwczovL21haWxmb29nYWUuYXBwc3BvdC5jb20vdD9zZW5kZXI9YWFuVnNhV1Z1UUhkbGNHOXdjQzVqYjIwJTNEJmFtcDt0eXBlPXplcm9jb250ZW50JmFtcDtndWlkPTBiMWRiODRkLTFmMTctNDE1MS05ZmE5LTUxMWNiYTg0ODAyMiI-PGZvbnQgY29sb3I9IiNmZmZmZmYiIHNpemU9IjEiPuGQpzwvZm9udD48L2Rpdj48L2Rpdj4NCjwvYmxvY2txdW90ZT48L2Rpdj48YnI-"}}]}, :sizeEstimate=>2597} 105 | end 106 | 107 | 108 | 109 | def test_unread_message(params = {}) 110 | {:id=>"14aecf9e31d945ba", :threadId=>"14aecf708ee65122", :labelIds=>["IMPORTANT", "CATEGORY_PERSONAL","UNREAD"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: >coucou >", :historyId=>"232799", :payload=>{:partId=>"", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"To", :value=>"Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Julien Hobeika , Nicolas Marlier "}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, :sizeEstimate=>1089} 111 | end 112 | 113 | def test_sent_message(params = {}) 114 | {:id=>"14aecf9e31d945ba", :threadId=>"14aecf708ee65122", :labelIds=>["IMPORTANT", "CATEGORY_PERSONAL","SENT"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: >coucou >", :historyId=>"232799", :payload=>{:partId=>"", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"To", :value=>"Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Julien Hobeika , Nicolas Marlier "}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, :sizeEstimate=>1089} 115 | end 116 | 117 | def test_inbox_message(params = {}) 118 | {:id=>"14aecf9e31d945ba", :threadId=>"14aecf708ee65122", :labelIds=>["IMPORTANT", "CATEGORY_PERSONAL","INBOX"], :snippet=>"test Thu, 15 Jan 2015 10:39:59 +0100 Julien Hobeika <julien@juliedesk.com>: >coucou >", :historyId=>"232799", :payload=>{:partId=>"", :mimeType=>"text/plain", :filename=>"", :headers=>[{:name=>"Received", :value=>"from 992877924458-i6nbjvo0fspv8hpmpss0i9aglneqrdpm.apps.googleusercontent.com named unknown by gmailapi.google.com with HTTPREST; Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"Date", :value=>"Thu, 15 Jan 2015 01:43:27 -0800"}, {:name=>"To", :value=>"Julien Hobeika "}, {:name=>"Cc", :value=>"Julien Hobeika , Julien Hobeika , Nicolas Marlier "}, {:name=>"Message-Id", :value=>""}, {:name=>"In-Reply-To", :value=>""}, {:name=>"References", :value=>""}, {:name=>"Subject", :value=>"test reply message"}, {:name=>"Mime-Version", :value=>"1.0"}, {:name=>"Content-Type", :value=>"text/plain; charset=UTF-8"}, {:name=>"Content-Transfer-Encoding", :value=>"quoted-printable"}, {:name=>"From", :value=>"julie@juliedesk.com"}], :body=>{:size=>238, :data=>"dGVzdA0KDQpUaHUsIDE1IEphbiAyMDE1IDEwOjM5OjU5ICswMTAwIEp1bGllbiBIb2JlaWthIDxqdWxpZW5AanVsaWVkZXNrLmNvbT46DQoNCj5jb3Vjb3UNCj4NCj4NCj4qSnVsaWVuIEhvYmVpa2EgfCBDby1Gb3VuZGVyICYgQ0VPKkVtYWlsOiBqdWxpZW5AanVsaWVkZXNrLmNvbQ0KPlRlbDogKzMzIDYgNjMgMzMgMTcgNTUNCj4NCj4qSnVsaWUgRGVzayoNCj53d3cuanVsaWVkZXNrLmNvbQ0KPg0KPg0KPuGQpw0KPg=="}}, :sizeEstimate=>1089} 119 | end 120 | 121 | def test_message_list 122 | {:messages=>[{:id=>"14ae456f2ff1e3e4", :threadId=>"14ae456f2ff1e3e4"}, {:id=>"14ae456891f20924", :threadId=>"14ae4438ed604170"}, {:id=>"14ae455204ed507e", :threadId=>"14ae4438ed604170"}, {:id=>"14ae45432aaf0c5a", :threadId=>"14ae4438ed604170"}, {:id=>"14ae4438ed604170", :threadId=>"14ae4438ed604170"}], :nextPageToken=>"05968490989102563512", :resultSizeEstimate=>20} 123 | end 124 | 125 | 126 | end 127 | end -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'gmail' 2 | require 'test/unit' 3 | require 'mocha/setup' 4 | require 'stringio' 5 | require 'shoulda' 6 | require File.expand_path('../test_data', __FILE__) 7 | 8 | # monkeypatch request methods 9 | module Gmail 10 | @client = nil 11 | 12 | def self.connect 13 | 14 | end 15 | 16 | def self.client= value 17 | @client = value 18 | end 19 | 20 | end 21 | 22 | class Test::Unit::TestCase 23 | include Gmail::TestData 24 | include Mocha 25 | 26 | setup do 27 | @mock = mock 28 | Gmail.client = @mock 29 | Gmail.new(client_id: "foo", client_secret: "foo", refresh_token: "foo", application_name: "test", application_version: "test") 30 | end 31 | 32 | teardown do 33 | Gmail.client = nil 34 | Gmail.client_id = nil 35 | Gmail.client_secret = nil 36 | Gmail.refresh_token = nil 37 | end 38 | end --------------------------------------------------------------------------------