context-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/Gemfile +4 -0
  2. data/LICENSE +21 -0
  3. data/README.md +129 -0
  4. data/Rakefile +121 -0
  5. data/SPEC.md +49 -0
  6. data/context-io.gemspec +96 -0
  7. data/lib/context-io.rb +14 -0
  8. data/lib/context-io/account.rb +254 -0
  9. data/lib/context-io/authentication.rb +23 -0
  10. data/lib/context-io/config.rb +103 -0
  11. data/lib/context-io/connection.rb +45 -0
  12. data/lib/context-io/core_ext/hash.rb +31 -0
  13. data/lib/context-io/error.rb +24 -0
  14. data/lib/context-io/error/bad_request.rb +12 -0
  15. data/lib/context-io/error/client_error.rb +10 -0
  16. data/lib/context-io/error/forbidden.rb +12 -0
  17. data/lib/context-io/error/internal_server_error.rb +10 -0
  18. data/lib/context-io/error/not_found.rb +12 -0
  19. data/lib/context-io/error/payment_required.rb +13 -0
  20. data/lib/context-io/error/server_error.rb +10 -0
  21. data/lib/context-io/error/service_unavailable.rb +13 -0
  22. data/lib/context-io/error/unauthorized.rb +12 -0
  23. data/lib/context-io/file.rb +234 -0
  24. data/lib/context-io/folder.rb +90 -0
  25. data/lib/context-io/message.rb +160 -0
  26. data/lib/context-io/oauth_provider.rb +84 -0
  27. data/lib/context-io/request.rb +70 -0
  28. data/lib/context-io/request/oauth.rb +44 -0
  29. data/lib/context-io/resource.rb +16 -0
  30. data/lib/context-io/response.rb +5 -0
  31. data/lib/context-io/response/parse_json.rb +30 -0
  32. data/lib/context-io/response/raise_client_error.rb +59 -0
  33. data/lib/context-io/response/raise_server_error.rb +32 -0
  34. data/lib/context-io/source.rb +193 -0
  35. data/lib/context-io/version.rb +7 -0
  36. data/spec/account_spec.rb +247 -0
  37. data/spec/contextio_spec.rb +45 -0
  38. data/spec/file_spec.rb +101 -0
  39. data/spec/fixtures/accounts.json +21 -0
  40. data/spec/fixtures/files.json +41 -0
  41. data/spec/fixtures/files_group.json +47 -0
  42. data/spec/fixtures/folders.json +1 -0
  43. data/spec/fixtures/messages.json +1 -0
  44. data/spec/fixtures/oauth_providers.json +12 -0
  45. data/spec/fixtures/sources.json +1 -0
  46. data/spec/folder_spec.rb +48 -0
  47. data/spec/message_spec.rb +294 -0
  48. data/spec/oauth_provider_spec.rb +88 -0
  49. data/spec/source_spec.rb +248 -0
  50. data/spec/spec_helper.rb +4 -0
  51. metadata +214 -0
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContextIO do
4
+ after do
5
+ ContextIO.reset
6
+ end
7
+
8
+ describe '.adapter' do
9
+ it 'should return the default adapter' do
10
+ ContextIO.adapter.should == ContextIO::Config::DEFAULT_ADAPTER
11
+ end
12
+ end
13
+
14
+ describe '.adapter=' do
15
+ it 'should set the adapter' do
16
+ ContextIO.adapter = :typhoeus
17
+ ContextIO.adapter.should == :typhoeus
18
+ end
19
+ end
20
+
21
+ describe '.user_agent' do
22
+ it 'should return the default user agent' do
23
+ ContextIO.user_agent.should == ContextIO::Config::DEFAULT_USER_AGENT
24
+ end
25
+ end
26
+
27
+ describe '.user_agent=' do
28
+ it 'should set the user agent' do
29
+ ContextIO.user_agent = 'Custom User Agent'
30
+ ContextIO.user_agent.should == 'Custom User Agent'
31
+ end
32
+ end
33
+
34
+ describe '.configure' do
35
+ ContextIO::Config::VALID_OPTIONS_KEYS.each do |key|
36
+ it "should set the #{key}" do
37
+ ContextIO.configure do |config|
38
+ config.send("#{key}=", key)
39
+ ContextIO.send(key).should == key
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContextIO::File do
4
+ before(:each) do
5
+ @fixtures_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures'))
6
+ account_id = 'abcdef1234567890'
7
+ @account = ContextIO::Account.new
8
+ @account.instance_eval do
9
+ # TODO: Tests shouldn't rely on internal state...
10
+ @id = account_id
11
+ end
12
+ @files_url = "https://api.context.io/2.0/accounts/#{account_id}/files"
13
+ end
14
+
15
+ describe '.all' do
16
+ before(:each) do
17
+ json_files = File.read(File.join(@fixtures_path, 'files.json'))
18
+ @request = stub_request(:get, @files_url).to_return(:body => json_files)
19
+ end
20
+
21
+ it 'returns an array of File objects' do
22
+ ContextIO::File.all(@account).first.should be_a(ContextIO::File)
23
+ end
24
+
25
+ it 'calls the API request' do
26
+ ContextIO::File.all(@account)
27
+
28
+ @request.should have_been_requested
29
+ end
30
+
31
+ it 'sends a query if one is given' do
32
+ @request.with(:query => { :email => 'me@example.com'})
33
+
34
+ ContextIO::File.all(@account, :email => 'me@example.com')
35
+
36
+ @request.should have_been_requested
37
+ end
38
+
39
+ it 'supports searching for a filename with a regexp' do
40
+ @request.with(:query => { :file_name => '/\.pdf$/'})
41
+
42
+ ContextIO::File.all(@account, :file_name => /\.pdf$/)
43
+
44
+ @request.should have_been_requested
45
+ end
46
+
47
+ it 'converts Time objects to integer timestamps' do
48
+ time = Time.now
49
+ @request.with(:query => { :date_before => time.to_i.to_s })
50
+
51
+ ContextIO::File.all(@account, :date_before => time)
52
+
53
+ @request.should have_been_requested
54
+ end
55
+
56
+ context 'group_by_revisions' do
57
+ before(:each) do
58
+ json_files = File.read(File.join(@fixtures_path, 'files_group.json'))
59
+ @request = stub_request(:get, @files_url).
60
+ with(:query => { :group_by_revisions => '1' }).
61
+ to_return(:body => json_files)
62
+ end
63
+
64
+ it 'converts occurences to File objects' do
65
+ files = ContextIO::File.all(@account, :group_by_revisions => true)
66
+ files.first[:occurences].first.should be_a(ContextIO::File)
67
+ end
68
+
69
+ it 'converts latest_date to a Time object' do
70
+ files = ContextIO::File.all(@account, :group_by_revisions => true)
71
+ files.first[:latest_date].should be_a(Time)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#content' do
77
+ before(:each) do
78
+ json_files = MultiJson.decode(File.read(File.join(@fixtures_path, 'files.json')))
79
+ file = json_files.first
80
+ @file_url = "#@files_url/#{file['file_id']}"
81
+ @request = stub_request(:get, @file_url).to_return(:body => MultiJson.encode(file))
82
+ @file = ContextIO::File.from_json(@account.id, file)
83
+ end
84
+
85
+ it 'calls the API request' do
86
+ request = stub_request(:get, "#@file_url/content")
87
+
88
+ @file.content
89
+
90
+ request.should have_been_requested
91
+ end
92
+
93
+ it 'does not parse the response' do
94
+ content = '{ "foo": "bar" }'
95
+ request = stub_request(:get, "#@file_url/content").
96
+ to_return(:body => content)
97
+
98
+ @file.content.should == content
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,21 @@
1
+ [{"id": "abcdef0123456789",
2
+ "username": "me.example.com_1234567890abcdef",
3
+ "created": 1234567890,
4
+ "suspended": 0,
5
+ "email_addresses": [ "me@example.com" ],
6
+ "first_name": "John",
7
+ "last_name": "Doe",
8
+ "password_expired": 0,
9
+ "sources": [{
10
+ "server": "mail.example.com",
11
+ "label": "me::mail.example.com",
12
+ "username": "me",
13
+ "port": 993,
14
+ "authentication_type": "password",
15
+ "use_ssl": true,
16
+ "sync_period": "1d",
17
+ "status": "OK",
18
+ "service_level": "pro",
19
+ "type": "imap"
20
+ }]
21
+ }]
@@ -0,0 +1,41 @@
1
+ [
2
+ {
3
+ "size": 123456,
4
+ "type": "application/octet-stream",
5
+ "subject": "A subject",
6
+ "date": 1234567890,
7
+ "addresses": {
8
+ "from": {
9
+ "email": "you@example.com"
10
+ },
11
+ "to": [{
12
+ "email": "me@example.com"
13
+ }]
14
+ },
15
+ "file_name": "a_file.pdf",
16
+ "body_section": 3,
17
+ "file_id": "0987654321abcdef",
18
+ "supports_preview": true,
19
+ "message_id": "0987654321fedcba",
20
+ "date_indexed": 1234567900,
21
+ "email_message_id": "<0192837465ABCDEFABCDEF@ABCDEFGHIJK>",
22
+ "person_info": {
23
+ "you@example.com": {
24
+ "thumbnail": "http://example.com/you.png"
25
+ },
26
+ "me@example.com": {
27
+ "thumbnail": "http://example.com/me.com"
28
+ }
29
+ },
30
+ "file_name_structure": [
31
+ [
32
+ "a_file",
33
+ "main"
34
+ ],
35
+ [
36
+ ".pdf",
37
+ "ext"
38
+ ]
39
+ ]
40
+ }
41
+ ]
@@ -0,0 +1,47 @@
1
+ [
2
+ {
3
+ "occurences": [
4
+ {
5
+ "size": 123456,
6
+ "type": "application/octet-stream",
7
+ "subject": "A subject",
8
+ "date": 1234567890,
9
+ "addresses": {
10
+ "from": {
11
+ "email": "you@example.com"
12
+ },
13
+ "to": [{
14
+ "email": "me@example.com"
15
+ }]
16
+ },
17
+ "file_name": "a_file.pdf",
18
+ "body_section": 3,
19
+ "file_id": "0987654321abcdef",
20
+ "supports_preview": true,
21
+ "message_id": "0987654321fedcba",
22
+ "date_indexed": 1234567900,
23
+ "email_message_id": "<0192837465ABCDEFABCDEF@ABCDEFGHIJK>",
24
+ "person_info": {
25
+ "you@example.com": {
26
+ "thumbnail": "http://example.com/you.png"
27
+ },
28
+ "me@example.com": {
29
+ "thumbnail": "http://example.com/me.com"
30
+ }
31
+ },
32
+ "file_name_structure": [
33
+ [
34
+ "a_file",
35
+ "main"
36
+ ],
37
+ [
38
+ ".pdf",
39
+ "ext"
40
+ ]
41
+ ]
42
+ }
43
+ ],
44
+ "file_name": "a_file.pdf",
45
+ "latest_date": 1234567890
46
+ }
47
+ ]
@@ -0,0 +1 @@
1
+ [{"name":"Follow up","nb_messages":"0","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/"},{"name":"INBOX","nb_messages":"15","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/"},{"name":"Misc","nb_messages":"0","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/"},{"name":"Priority","nb_messages":"0","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/"},{"name":"[Gmail]/All Mail","nb_messages":20,"delim":"/","symbolic_name":"\\AllMail","included_in_sync":true},{"name":"[Gmail]/Drafts","nb_messages":"0","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/","symbolic_name":"\\Drafts"},{"name":"[Gmail]/Important","nb_messages":"3","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/","symbolic_name":"\\Important"},{"name":"[Gmail]/Sent Mail","nb_messages":"1","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/","symbolic_name":"\\Sent"},{"name":"[Gmail]/Starred","nb_messages":"0","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":false,"Noselect":false,"HasNoChildren":true},"delim":"/","symbolic_name":"\\Starred"},{"name":"[Gmail]/Trash","nb_messages":"1","attributes":{"Noinferiors":false,"Marked":false,"HasChildren":true,"Noselect":false,"HasNoChildren":true},"delim":"/","symbolic_name":"\\Trash"}]
@@ -0,0 +1 @@
1
+ [{"date_indexed":1326390355,"person_info":{"james@example.com":{"thumbnail":"https://secure.gravatar.com/avatar/da027b7f882991709b63a08e7e300221?s=50&d=https%3A%2F%2Fapp.dokdok.com%2Findex.php%3Fdo%3Dusrgetimg%26id%3D-1"},"mail-noreply@google.com":{"thumbnail":"https://secure.gravatar.com/avatar/eee980fd43816c612e888a1b63907aef?s=50&d=https%3A%2F%2Fapp.dokdok.com%2Findex.php%3Fdo%3Dusrgetimg%26id%3D-1"}},"gmail_message_id":"1339e889bae8fff8","email_message_id":"<CA+him2-bVdNrrugkU7rkDtj-Wn-gk2d=bPiBBWk+iagtVV1csQ@mail.gmail.com>","addresses":{"from":{"name":"Gmail Team","email":"mail-noreply@google.com"},"to":[{"name":"James Dean","email":"james@example.com"}]},"subject":"Get Gmail on your mobile phone","date":1321214712,"gmail_thread_id":"1339e889bae8fff8","sources":[{"label":"james@example.com::imap.googlemail.com","resource_url":"https://api.context.io/2.0/accounts/4f0f1c4f3f757edc3b000002/sources/james%40example.com%3A%3Aimap.googlemail.com"}],"folders":["\\Inbox","INBOX"],"message_id":"4f0f1c533f757e0f3c00000b"},{"date_indexed":1326390355,"person_info":{"james@example.com":{"thumbnail":"https://secure.gravatar.com/avatar/da027b7f882991709b63a08e7e300221?s=50&d=https%3A%2F%2Fapp.dokdok.com%2Findex.php%3Fdo%3Dusrgetimg%26id%3D-1"},"mail-noreply@google.com":{"thumbnail":"https://secure.gravatar.com/avatar/eee980fd43816c612e888a1b63907aef?s=50&d=https%3A%2F%2Fapp.dokdok.com%2Findex.php%3Fdo%3Dusrgetimg%26id%3D-1"}},"gmail_message_id":"1339e889bcda5676","email_message_id":"<CA+him2_6bjYfmDmM9ai5N3bDovACCKUwb5XWCc1XRn=7LdTsQA@mail.gmail.com>","addresses":{"from":{"name":"Gmail Team","email":"mail-noreply@google.com"},"to":[{"name":"James Dean","email":"james@example.com"}]},"subject":"Customize Gmail with colors and themes","date":1321214712,"gmail_thread_id":"1339e889bcda5676","sources":[{"label":"james@example.com::imap.googlemail.com","resource_url":"https://api.context.io/2.0/accounts/4f0f1c4f3f757edc3b000002/sources/james%40example.com%3A%3Aimap.googlemail.com"}],"folders":["\\Inbox","INBOX"],"message_id":"4f0f1c533f757e0f3c00000c"}]
@@ -0,0 +1,12 @@
1
+ [
2
+ {
3
+ "type": "GMAIL",
4
+ "provider_consumer_key": "1qa2ws3ed",
5
+ "provider_consumer_secret": "aq1sw2de3fr4gt5"
6
+ },
7
+ {
8
+ "type": "GOOGLEAPPSMARKETPLACE",
9
+ "provider_consumer_key": "qazwsxedc",
10
+ "provider_consumer_secret": "zaqxswcdevfrbgt"
11
+ }
12
+ ]
@@ -0,0 +1 @@
1
+ [{"label":"bi@example.com::imap.example.com","authentication_type":"oauth","port":993,"service_level":"pro","username":"bi@example.com","server":"imap.example.com","type":"imap","sync_period":"1d","use_ssl":true,"status":"OK"}]
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContextIO::Folder do
4
+ before(:each) do
5
+ @fixtures_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures'))
6
+ account_id = 'abcdef1234567890'
7
+ @account = ContextIO::Account.new
8
+ @account.instance_eval do
9
+ @id = account_id
10
+ end
11
+ source_label = 'me@example.com::imap.example.com'
12
+ @source = ContextIO::Source.new(account_id)
13
+ @source.instance_eval { @label = source_label }
14
+
15
+ @folders_url = "https://api.context.io/2.0/accounts/#{account_id}/sources/#{source_label}/folders"
16
+ end
17
+
18
+ describe '.all' do
19
+ before(:each) do
20
+ json_folders = File.read(File.join(@fixtures_path, 'folders.json'))
21
+ @response = stub_request(:get, @folders_url).
22
+ to_return(:body => json_folders)
23
+ end
24
+
25
+ it 'accepts a Source object as an argument' do
26
+ expect { ContextIO::Folder.all(@source) }.to_not raise_error
27
+ end
28
+
29
+ it 'accepts an account ID and a source label as arguments' do
30
+ expect { ContextIO::Folder.all(@account.id, @source.label) }.to_not raise_error
31
+ end
32
+
33
+ it 'returns an array of Folder objects' do
34
+ folders = ContextIO::Folder.all(@source)
35
+ folders.first.should be_a(ContextIO::Folder)
36
+ end
37
+
38
+ it 'calls the API method' do
39
+ ContextIO::Folder.all(@source)
40
+ @response.should have_been_requested
41
+ end
42
+
43
+ it 'sets the name of Folder object' do
44
+ folder = ContextIO::Folder.all(@source).first
45
+ folder.name.should == 'Follow up'
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,294 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContextIO::Message do
4
+ before(:each) do
5
+ @fixtures_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures'))
6
+ account_id = 'abcdef1234567890'
7
+ @account = ContextIO::Account.new
8
+ @account.instance_eval do
9
+ @id = account_id
10
+ end
11
+ @messages_url = "https://api.context.io/2.0/accounts/#{account_id}/messages"
12
+
13
+ end
14
+
15
+ describe '.all' do
16
+ before(:each) do
17
+ json_messages = File.read(File.join(@fixtures_path, 'messages.json'))
18
+ @response = stub_request(:get, @messages_url).
19
+ to_return(:body => json_messages)
20
+ end
21
+
22
+ it 'returns an array of Message objects for given account ID' do
23
+ messages = ContextIO::Message.all(@account.id)
24
+ messages.should be_a(Array)
25
+ messages.first.should be_a(ContextIO::Message)
26
+ end
27
+
28
+ it 'returns an array of Message objects for given Account object' do
29
+ messages = ContextIO::Message.all(@account)
30
+ messages.should be_a(Array)
31
+ messages.first.should be_a(ContextIO::Message)
32
+ end
33
+
34
+ it 'returns empty array when no account is given' do
35
+ messages = ContextIO::Message.all(nil)
36
+ messages.should be_a(Array)
37
+ messages.length.should == 0
38
+ end
39
+
40
+ it 'calls the API method' do
41
+ ContextIO::Message.all(@account)
42
+ @response.should have_been_requested
43
+ end
44
+
45
+ it 'sets attributes of Message object' do
46
+ msg = ContextIO::Message.all(@account).first
47
+ msg.message_id.should == '4f0f1c533f757e0f3c00000b'
48
+ msg.subject.should == 'Get Gmail on your mobile phone'
49
+ msg.from['name'].should == 'Gmail Team'
50
+ msg.to.length.should == 1
51
+ end
52
+
53
+ it 'sends query' do
54
+ q = {
55
+ :subject => 'Some subject',
56
+ :email => 'james@example.net',
57
+ :to => 'james@example.com',
58
+ :limit => '30',
59
+ :offset => '30'
60
+ }
61
+
62
+ @response = @response.with(:query => q)
63
+
64
+ ContextIO::Message.all(@account, q)
65
+ @response.should have_been_requested
66
+ end
67
+ end
68
+
69
+ describe 'message flags' do
70
+ before(:each) do
71
+ @msg_id = '4f0f1c533f757e0f3c00000b'
72
+ json_messages = File.read(File.join(@fixtures_path, 'messages.json'))
73
+ @response = stub_request(:get, @messages_url).
74
+ to_return(:body => json_messages)
75
+ end
76
+
77
+ it 'retrieves flags' do
78
+ flags_response = stub_request(:get, "#{@messages_url}/#{@msg_id}/flags").
79
+ to_return(:body => '{"answered":false,"draft":false,"deleted":false,"seen":true,"flagged":false}')
80
+ flags = ContextIO::Message.all(@account).first.flags
81
+ flags.should be_a(Hash)
82
+ flags['seen'].should == true
83
+ end
84
+
85
+ it 'sets read flag' do
86
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
87
+ with(:body => { "seen" => "true" }).
88
+ to_return(:body => '{"success":true}')
89
+ ContextIO::Message.all(@account).first.read!.should be_true
90
+ end
91
+
92
+ it 'resets read flag' do
93
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
94
+ with(:body => { "seen" => "false" }).
95
+ to_return(:body => '{"success":true}')
96
+ ContextIO::Message.all(@account).first.unread!.should be_true
97
+ end
98
+
99
+ it 'sets flagged flag' do
100
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
101
+ with(:body => { "flagged" => "true" }).
102
+ to_return(:body => '{"success":true}')
103
+ ContextIO::Message.all(@account).first.flagged!.should be_true
104
+ end
105
+
106
+ it 'resets flagged flag' do
107
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
108
+ with(:body => { "flagged" => "false" }).
109
+ to_return(:body => '{"success":true}')
110
+ ContextIO::Message.all(@account).first.unflagged!.should be_true
111
+ end
112
+
113
+ it 'sets answered flag' do
114
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
115
+ with(:body => { "answered" => "true" }).
116
+ to_return(:body => '{"success":true}')
117
+ ContextIO::Message.all(@account).first.answered!.should be_true
118
+ end
119
+
120
+ it 'resets answered flag' do
121
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
122
+ with(:body => { "answered" => "false" }).
123
+ to_return(:body => '{"success":true}')
124
+ ContextIO::Message.all(@account).first.unanswered!.should be_true
125
+ end
126
+
127
+ it 'sets draft flag' do
128
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
129
+ with(:body => { "draft" => "true" }).
130
+ to_return(:body => '{"success":true}')
131
+ ContextIO::Message.all(@account).first.draft!.should be_true
132
+ end
133
+
134
+ it 'resets draft flag' do
135
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
136
+ with(:body => { "draft" => "false" }).
137
+ to_return(:body => '{"success":true}')
138
+ ContextIO::Message.all(@account).first.undraft!.should be_true
139
+ end
140
+
141
+ it 'sets deleted flag' do
142
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
143
+ with(:body => { "deleted" => "true" }).
144
+ to_return(:body => '{"success":true}')
145
+ ContextIO::Message.all(@account).first.delete!.should be_true
146
+ end
147
+
148
+ it 'resets deleted flag' do
149
+ flags_response = stub_request(:post, "#{@messages_url}/#{@msg_id}/flags").
150
+ with(:body => { "deleted" => "false" }).
151
+ to_return(:body => '{"success":true}')
152
+ ContextIO::Message.all(@account).first.undelete!.should be_true
153
+ end
154
+ end
155
+
156
+ describe 'thread' do
157
+ before(:each) do
158
+ json_messages = File.read(File.join(@fixtures_path, 'messages.json'))
159
+ thread_messages = "{\"messages\": #{json_messages}}"
160
+ stub_request(:get, @messages_url).to_return(:body => json_messages)
161
+ msg_id = '4f0f1c533f757e0f3c00000b'
162
+ @response = stub_request(:get, "#{@messages_url}/#{msg_id}/thread").
163
+ to_return(:body => thread_messages)
164
+ end
165
+
166
+ it 'returns array of Message objects' do
167
+ thread = ContextIO::Message.all(@account).first.thread
168
+ thread.should be_a(Array)
169
+ thread.first.should be_a(ContextIO::Message)
170
+ end
171
+
172
+ it 'calls API method' do
173
+ ContextIO::Message.all(@account).first.thread
174
+ @response.should have_been_requested
175
+ end
176
+ end
177
+
178
+ describe 'body and headers lazy loading' do
179
+ before(:each) do
180
+ msg_id = '4f0f1c533f757e0f3c00000b'
181
+ body = '[
182
+ {
183
+ "type": "text/plain",
184
+ "content":"Just a message"
185
+ },
186
+ {
187
+ "type": "text/html",
188
+ "content": "<html><p>Just a message</p></html>"
189
+ }]'
190
+ headers = '{"Received":"by 10.10.1.1"}'
191
+ json_messages = File.read(File.join(@fixtures_path, 'messages.json'))
192
+ stub_request(:get, @messages_url).
193
+ to_return(:body => json_messages)
194
+ @body_resp = stub_request(:get, "#{@messages_url}/#{msg_id}/body").
195
+ to_return(:body => body)
196
+ @headers_resp = stub_request(:get, "#{@messages_url}/#{msg_id}/headers").
197
+ to_return(:body => headers)
198
+ end
199
+
200
+ it 'requests body on first access' do
201
+ msg = ContextIO::Message.all(@account).first
202
+ msg.body.should == 'Just a message'
203
+ msg.body('html').start_with?('<html>').should be_true
204
+ @body_resp.should have_been_requested
205
+ end
206
+
207
+ it 'does not request body on second request' do
208
+ msg = ContextIO::Message.all(@account).first
209
+ msg.body
210
+ msg.body
211
+ @body_resp.should have_been_made.once
212
+ end
213
+
214
+ it 'requests headers' do
215
+ msg = ContextIO::Message.all(@account).first
216
+ msg.headers.should be_a(Hash)
217
+ msg.headers['Received'].should == 'by 10.10.1.1'
218
+ @headers_resp.should have_been_requested
219
+ end
220
+
221
+ it 'does not request headers on second request' do
222
+ msg = ContextIO::Message.all(@account).first
223
+ msg.headers
224
+ msg.headers
225
+ @headers_resp.should have_been_made.once
226
+ end
227
+ end
228
+
229
+ describe "copy" do
230
+ before(:each) do
231
+ msgs = MultiJson.decode(File.read(File.join(@fixtures_path, 'messages.json')))
232
+ @message = ContextIO::Message.from_json(@account.id, msgs.first)
233
+ copy_options = {:dst_folder => "Important", :move => '0'}
234
+ copy_to_source = {:dst_folder => "Important", :dst_source => "Other Source", :move => '0'}
235
+ @copy_response = stub_request(:post, "#{@messages_url}/#{@message.message_id}").
236
+ with(:body => copy_options)
237
+ @copy_to_source_response = stub_request(:post, "#{@messages_url}/#{@message.message_id}").
238
+ with(:body => copy_to_source)
239
+ end
240
+
241
+ it 'raises ArgumentError for empty target folder' do
242
+ lambda { @message.copy(nil) }.should raise_error ArgumentError
243
+ end
244
+
245
+ it "copy calls API method with copy options" do
246
+ @message.copy("Important")
247
+ @copy_response.should have_been_requested
248
+ end
249
+
250
+ it "copy to source calls API method with source in post arguments" do
251
+ @message.copy("Important", "Other Source")
252
+ @copy_to_source_response.should have_been_requested
253
+ end
254
+ end
255
+
256
+ describe "move" do
257
+ before(:each) do
258
+ msgs = MultiJson.decode(File.read(File.join(@fixtures_path, 'messages.json')))
259
+ @message = ContextIO::Message.from_json(@account.id, msgs.first)
260
+ move_options = {:dst_folder => "Important", :move => '1'}
261
+ @move_response = stub_request(:post, "#{@messages_url}/#{@message.message_id}").
262
+ with(:body => move_options)
263
+ end
264
+
265
+ it 'raises ArgumentError for empty target folder' do
266
+ lambda { @message.move(nil) }.should raise_error ArgumentError
267
+ end
268
+
269
+ it "move calls API method with move options" do
270
+ @message.move("Important")
271
+ @move_response.should have_been_requested
272
+ end
273
+ end
274
+
275
+ describe '.find' do
276
+ before(:each) do
277
+ @messages = MultiJson.decode(File.read(File.join(@fixtures_path, 'messages.json')))
278
+ @find_url = "#{@messages_url}/#{@messages.first['message_id']}"
279
+ @response = stub_request(:get, @find_url).
280
+ to_return(:body => MultiJson.encode(@messages.first))
281
+ end
282
+
283
+ it 'calls API method' do
284
+ ContextIO::Message.find(@account, @messages.first['message_id'])
285
+ @response.should have_been_requested
286
+ end
287
+
288
+ it 'returns single message for given ID' do
289
+ msg = ContextIO::Message.find(@account, @messages.first['message_id'])
290
+ msg.should be_a(ContextIO::Message)
291
+ msg.message_id.should == @messages.first['message_id']
292
+ end
293
+ end
294
+ end