context-io 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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