imap-backup 1.4.2 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@ module BackupDirectoryHelpers
3
3
  from = fixture("connection")[:username]
4
4
  subject = options[:subject]
5
5
  body = options[:body]
6
- body_and_headers = <<-EOT.gsub("\n", "\r\n")
6
+ body_and_headers = <<-EOT
7
7
  From: #{from}
8
8
  Subject: #{subject}
9
9
 
@@ -13,6 +13,14 @@ Subject: #{subject}
13
13
  "From #{from}\n#{body_and_headers}\n"
14
14
  end
15
15
 
16
+ def imap_data(uid_validity, uids)
17
+ {
18
+ version: 2,
19
+ uid_validity: uid_validity,
20
+ uids: uids
21
+ }
22
+ end
23
+
16
24
  def mbox_content(name)
17
25
  File.read(mbox_path(name))
18
26
  end
@@ -20,6 +28,18 @@ Subject: #{subject}
20
28
  def mbox_path(name)
21
29
  File.join(local_backup_path, name + ".mbox")
22
30
  end
31
+
32
+ def imap_path(name)
33
+ File.join(local_backup_path, name + ".imap")
34
+ end
35
+
36
+ def imap_content(name)
37
+ File.read(imap_path(name))
38
+ end
39
+
40
+ def imap_parsed(name)
41
+ JSON.parse(imap_content(name), symbolize_names: true)
42
+ end
23
43
  end
24
44
 
25
45
  RSpec.configure do |config|
@@ -1,18 +1,43 @@
1
1
  module EmailServerHelpers
2
2
  REQUESTED_ATTRIBUTES = ["RFC822", "FLAGS", "INTERNALDATE"]
3
+ DEFAULT_EMAIL = "address@example.org"
3
4
 
4
5
  def send_email(folder, options)
5
- from = options[:from] || "address@example.org"
6
+ message = message_as_server_message(options)
7
+ imap.append(folder, message, nil, nil)
8
+ end
9
+
10
+ def message_as_server_message(options)
11
+ from = options[:from] || DEFAULT_EMAIL
6
12
  subject = options[:subject]
7
13
  body = options[:body]
8
- message = <<-EOT
14
+ message = <<-EOT.gsub("\n", "\r\n")
9
15
  From: #{from}
10
16
  Subject: #{subject}
11
17
 
12
18
  #{body}
19
+
13
20
  EOT
21
+ end
14
22
 
15
- imap.append(folder, message, nil, nil)
23
+ def server_messages(folder)
24
+ server_uids(folder).map do |uid|
25
+ server_fetch_email(folder, uid)
26
+ end
27
+ end
28
+
29
+ def server_message_to_body(message)
30
+ message["RFC822"]
31
+ end
32
+
33
+ def server_fetch_email(folder, uid)
34
+ examine folder
35
+ fetch_data_items = imap.uid_fetch([uid.to_i], REQUESTED_ATTRIBUTES)
36
+ return nil if fetch_data_items.nil?
37
+ fetch_data_item = fetch_data_items[0]
38
+ attributes = fetch_data_item.attr
39
+ attributes["RFC822"].force_encoding("utf-8")
40
+ attributes
16
41
  end
17
42
 
18
43
  def delete_emails(folder)
@@ -22,6 +47,44 @@ Subject: #{subject}
22
47
  imap.expunge
23
48
  end
24
49
 
50
+ def examine(folder)
51
+ imap.examine(folder)
52
+ end
53
+
54
+ def server_uids(folder)
55
+ examine(folder)
56
+ imap.uid_search(["ALL"]).sort
57
+ end
58
+
59
+ def server_uid_validity(folder)
60
+ examine(folder)
61
+ imap.responses["UIDVALIDITY"][0]
62
+ end
63
+
64
+ def server_folders
65
+ root_info = imap.list("", "")[0]
66
+ root = root_info.name
67
+ imap.list(root, "*")
68
+ end
69
+
70
+ def server_create_folder(folder)
71
+ imap.create(folder)
72
+ imap.disconnect
73
+ @imap = nil
74
+ end
75
+
76
+ def server_rename_folder(from, to)
77
+ imap.rename(from, to)
78
+ imap.disconnect
79
+ @imap = nil
80
+ end
81
+
82
+ def server_delete_folder(folder)
83
+ imap.delete folder
84
+ imap.disconnect
85
+ @imap = nil
86
+ end
87
+
25
88
  def imap
26
89
  @imap ||=
27
90
  begin
@@ -1,6 +1,8 @@
1
1
  shared_context "message-fixtures" do
2
2
  let(:uid1) { 123 }
3
3
  let(:uid2) { 345 }
4
+ let(:uid3) { 567 }
4
5
  let(:msg1) { {uid: uid1, subject: "Test 1", body: "body 1\nHi"} }
5
6
  let(:msg2) { {uid: uid2, subject: "Test 2", body: "body 2"} }
7
+ let(:msg3) { {uid: uid3, subject: "Test 3", body: "body 3"} }
6
8
  end
@@ -2,6 +2,5 @@
2
2
  :username: 'address@example.org'
3
3
  :password: 'pass'
4
4
  :connection_options:
5
- :port: 8993
6
- :ssl:
7
- :verify_mode: 0
5
+ :port: 8143
6
+ :ssl: false
@@ -10,7 +10,7 @@ describe Imap::Backup::Account::Connection do
10
10
  end
11
11
 
12
12
  let(:imap) do
13
- double("Net::IMAP", login: nil, disconnect: nil)
13
+ instance_double(Net::IMAP, login: nil, disconnect: nil)
14
14
  end
15
15
  let(:imap_folders) { [] }
16
16
  let(:options) do
@@ -86,7 +86,9 @@ describe Imap::Backup::Account::Connection do
86
86
  end
87
87
 
88
88
  context "#status" do
89
- let(:folder) { double("folder", uids: [remote_uid]) }
89
+ let(:folder) do
90
+ instance_double(Imap::Backup::Account::Folder, uids: [remote_uid])
91
+ end
90
92
  let(:local_uid) { "local_uid" }
91
93
  let(:serializer) do
92
94
  instance_double(Imap::Backup::Serializer::Mbox, uids: [local_uid])
@@ -112,9 +114,21 @@ describe Imap::Backup::Account::Connection do
112
114
  end
113
115
 
114
116
  context "#run_backup" do
115
- let(:folder) { double("folder", name: "folder") }
116
- let(:serializer) { double("serializer") }
117
- let(:downloader) { double(Imap::Backup::Downloader, run: nil) }
117
+ let(:folder) do
118
+ instance_double(
119
+ Imap::Backup::Account::Folder,
120
+ name: "folder",
121
+ uid_validity: uid_validity
122
+ )
123
+ end
124
+ let(:uid_validity) { 123 }
125
+ let(:serializer) do
126
+ instance_double(
127
+ Imap::Backup::Serializer::Mbox,
128
+ set_uid_validity: nil
129
+ )
130
+ end
131
+ let(:downloader) { instance_double(Imap::Backup::Downloader, run: nil) }
118
132
 
119
133
  before do
120
134
  allow(Imap::Backup::Downloader).
@@ -143,7 +143,8 @@ describe Imap::Backup::Configuration::Account do
143
143
  context "if the server is blank" do
144
144
  [
145
145
  ["GMail", "foo@gmail.com", "imap.gmail.com"],
146
- ["Fastmail", "bar@fastmail.fm", "mail.messagingengine.com"]
146
+ ["Fastmail", "bar@fastmail.fm", "imap.fastmail.com"],
147
+ ["Fastmail", "bar@fastmail.com", "imap.fastmail.com"]
147
148
  ].each do |service, email, expected|
148
149
  context service do
149
150
  let(:new_email) { email }
@@ -26,11 +26,7 @@ describe Email::Provider do
26
26
 
27
27
  describe "#options" do
28
28
  it "returns options" do
29
- expect(subject.options).to be_a(Hash)
30
- end
31
-
32
- it "forces TLSv1_2" do
33
- expect(subject.options[:ssl][:ssl_version]).to eq(:TLSv1_2)
29
+ expect(subject.options).to eq(port: 993, ssl: true)
34
30
  end
35
31
  end
36
32
 
@@ -1,119 +1,82 @@
1
- require "spec_helper"
2
-
3
1
  describe Imap::Backup::Serializer::Mbox do
4
- let(:stat) { double("File::Stat", mode: 0o700) }
5
2
  let(:base_path) { "/base/path" }
6
- let(:mbox_pathname) { "/base/path/my/folder.mbox" }
7
- let(:mbox_exists) { true }
8
- let(:imap_pathname) { "/base/path/my/folder.imap" }
9
- let(:imap_exists) { true }
3
+ let(:store) do
4
+ instance_double(
5
+ Imap::Backup::Serializer::MboxStore,
6
+ add: nil,
7
+ uids: nil
8
+ )
9
+ end
10
+ let(:imap_folder) { "folder" }
11
+ let(:permissions) { 0o700 }
12
+ let(:dir_exists) { true }
10
13
 
11
14
  before do
12
15
  allow(Imap::Backup::Utils).to receive(:make_folder)
13
- allow(File).to receive(:exist?).with(base_path).and_return(true)
14
- allow(File).to receive(:stat).with(base_path).and_return(stat)
15
- allow(File).to receive(:exist?).with(mbox_pathname).and_return(mbox_exists)
16
- allow(File).to receive(:exist?).with(imap_pathname).and_return(imap_exists)
16
+ allow(Imap::Backup::Utils).to receive(:mode) { permissions }
17
+ allow(Imap::Backup::Utils).to receive(:check_permissions) { true }
18
+ allow(File).to receive(:directory?) { dir_exists }
17
19
  end
18
20
 
19
- context "#initialize" do
20
- it "creates the containing directory" do
21
- described_class.new(base_path, "my/folder")
21
+ subject { described_class.new(base_path, imap_folder) }
22
22
 
23
- expect(Imap::Backup::Utils).
24
- to have_received(:make_folder).with(base_path, "my", 0o700)
25
- end
23
+ before do
24
+ allow(FileUtils).to receive(:chmod)
25
+ allow(Imap::Backup::Serializer::MboxStore).to receive(:new) { store }
26
+ end
26
27
 
27
- context "mbox and imap files" do
28
- context "if mbox exists and imap doesn't" do
29
- let(:imap_exists) { false }
28
+ context "containing directory" do
29
+ before { subject.uids }
30
30
 
31
- it "fails" do
32
- expect {
33
- described_class.new(base_path, "my/folder")
34
- }.to raise_error(RuntimeError, ".imap file missing")
35
- end
36
- end
31
+ context "when the IMAP folder has multiple elements" do
32
+ let(:imap_folder) { "folder/path" }
37
33
 
38
- context "if imap exists and mbox doesn't" do
39
- let(:mbox_exists) { false }
34
+ context "when the containing directory is missing" do
35
+ let(:dir_exists) { false }
40
36
 
41
- it "fails" do
42
- expect {
43
- described_class.new(base_path, "my/folder")
44
- }.to raise_error(RuntimeError, ".mbox file missing")
37
+ it "is created" do
38
+ expect(Imap::Backup::Utils).to have_received(:make_folder).
39
+ with(base_path, File.dirname(imap_folder), 0o700)
45
40
  end
46
41
  end
47
42
  end
48
- end
49
43
 
50
- context "instance methods" do
51
- let(:ids) { %w(3 2 1) }
44
+ context "when the containing directory permissons are incorrect" do
45
+ let(:permissions) { 0o777 }
52
46
 
53
- before do
54
- allow(CSV).to receive(:foreach) { |&b| ids.each { |id| b.call [id] } }
55
- end
56
-
57
- subject { described_class.new(base_path, "my/folder") }
58
-
59
- context "#uids" do
60
- it "returns the backed-up uids as sorted integers" do
61
- expect(subject.uids).to eq(ids.map(&:to_i).sort)
62
- end
63
-
64
- context "if the mbox does not exist" do
65
- let(:mbox_exists) { false }
66
- let(:imap_exists) { false }
67
-
68
- it "returns an empty Array" do
69
- expect(subject.uids).to eq([])
70
- end
47
+ it "corrects them" do
48
+ path = File.expand_path(File.join(base_path, File.dirname(imap_folder)))
49
+ expect(FileUtils).to have_received(:chmod).with(0o700, path)
71
50
  end
72
51
  end
73
52
 
74
- context "#save" do
75
- let(:mbox_formatted_message) { "message in mbox format" }
76
- let(:message_uid) { "999" }
77
- let(:message) do
78
- double("Email::Mboxrd::Message", to_serialized: mbox_formatted_message)
53
+ context "when the containing directory permissons are correct" do
54
+ it "does nothing" do
55
+ expect(FileUtils).to_not have_received(:chmod)
79
56
  end
80
- let(:mbox_file) { double("File - mbox", write: nil, close: nil) }
81
- let(:imap_file) { double("File - imap", write: nil, close: nil) }
82
-
83
- before do
84
- allow(Email::Mboxrd::Message).to receive(:new).and_return(message)
85
- allow(File).to receive(:open).with(mbox_pathname, "ab") { mbox_file }
86
- allow(File).to receive(:open).with(imap_pathname, "ab") { imap_file }
87
- end
88
-
89
- it "saves the message to the mbox" do
90
- subject.save(message_uid, "The\nemail\n")
57
+ end
91
58
 
92
- expect(mbox_file).to have_received(:write).with(mbox_formatted_message)
59
+ context "when the containing directory exists" do
60
+ it "is not created" do
61
+ expect(Imap::Backup::Utils).to_not have_received(:make_folder).
62
+ with(base_path, File.dirname(imap_folder), 0o700)
93
63
  end
64
+ end
65
+ end
94
66
 
95
- it "saves the uid to the imap file" do
96
- subject.save(message_uid, "The\nemail\n")
97
-
98
- expect(imap_file).to have_received(:write).with(message_uid + "\n")
99
- end
67
+ context "#uids" do
68
+ it "calls the store" do
69
+ subject.uids
100
70
 
101
- context "when the message causes parsing errors" do
102
- before do
103
- allow(message).to receive(:to_serialized).and_raise(ArgumentError)
104
- end
71
+ expect(store).to have_received(:uids)
72
+ end
73
+ end
105
74
 
106
- it "skips the message" do
107
- subject.save(message_uid, "The\nemail\n")
108
- expect(mbox_file).to_not have_received(:write)
109
- end
75
+ context "#save" do
76
+ it "calls the store" do
77
+ subject.save("foo", "bar")
110
78
 
111
- it "does not fail" do
112
- expect do
113
- subject.save(message_uid, "The\nemail\n")
114
- end.to_not raise_error
115
- end
116
- end
79
+ expect(store).to have_received(:add)
117
80
  end
118
81
  end
119
82
  end
@@ -0,0 +1,117 @@
1
+ describe Imap::Backup::Serializer::MboxStore do
2
+ let(:base_path) { "/base/path" }
3
+ let(:folder) { "the/folder" }
4
+ let(:folder_path) { File.join(base_path, folder) }
5
+ let(:imap_pathname) { folder_path + ".imap" }
6
+ let(:imap_exists) { true }
7
+ let(:imap_file) { double("File - imap", write: nil, close: nil) }
8
+ let(:mbox_pathname) { folder_path + ".mbox" }
9
+ let(:mbox_exists) { true }
10
+ let(:mbox_file) { double("File - mbox", write: nil, close: nil) }
11
+ let(:uids) { [3, 2, 1] }
12
+ let(:imap_content) do
13
+ {
14
+ version: Imap::Backup::Serializer::MboxStore::CURRENT_VERSION,
15
+ uid_validity: 123,
16
+ uids: uids.sort
17
+ }.to_json
18
+ end
19
+
20
+ subject { described_class.new(base_path, folder) }
21
+
22
+ before do
23
+ allow(File).to receive(:exist?).and_call_original
24
+ allow(File).to receive(:exist?).with(imap_pathname) { imap_exists }
25
+ allow(File).to receive(:exist?).with(mbox_pathname) { mbox_exists }
26
+
27
+ allow(File).to receive(:open).and_call_original
28
+ allow(File).
29
+ to receive(:open).with("/base/path/my/folder.imap") { imap_content }
30
+ allow(File).to receive(:open).with(imap_pathname, "w").and_yield(imap_file)
31
+ allow(File).to receive(:open).with(mbox_pathname, "w").and_yield(mbox_file)
32
+
33
+ allow(File).to receive(:read).and_call_original
34
+ allow(File).to receive(:read).with(imap_pathname) { imap_content }
35
+
36
+ allow(File).to receive(:unlink).and_call_original
37
+ allow(File).to receive(:unlink).with(imap_pathname)
38
+ allow(File).to receive(:unlink).with(mbox_pathname)
39
+
40
+ allow(FileUtils).to receive(:chmod)
41
+ end
42
+
43
+ context "#uids" do
44
+ it "returns the backed-up uids as sorted integers" do
45
+ expect(subject.uids).to eq(uids.map(&:to_i).sort)
46
+ end
47
+
48
+ context "when the imap file does not exist" do
49
+ let(:imap_exists) { false }
50
+
51
+ it "returns an empty Array" do
52
+ expect(subject.uids).to eq([])
53
+ end
54
+ end
55
+
56
+ context "when the mbox does not exist" do
57
+ let(:mbox_exists) { false }
58
+
59
+ it "returns an empty Array" do
60
+ expect(subject.uids).to eq([])
61
+ end
62
+ end
63
+ end
64
+
65
+ context "#add" do
66
+ let(:mbox_formatted_message) { "message in mbox format" }
67
+ let(:message_uid) { "999" }
68
+ let(:message) do
69
+ instance_double(
70
+ Email::Mboxrd::Message,
71
+ to_serialized: mbox_formatted_message
72
+ )
73
+ end
74
+ let(:updated_imap_content) do
75
+ {
76
+ version: Imap::Backup::Serializer::MboxStore::CURRENT_VERSION,
77
+ uid_validity: 123,
78
+ uids: (uids + [999]).sort
79
+ }.to_json
80
+ end
81
+
82
+ before do
83
+ allow(Email::Mboxrd::Message).to receive(:new).and_return(message)
84
+ allow(File).to receive(:open).with(mbox_pathname, "ab") { mbox_file }
85
+ subject.uid_validity = 123
86
+ end
87
+
88
+ it "saves the message to the mbox" do
89
+ subject.add(message_uid, "The\nemail\n")
90
+
91
+ expect(mbox_file).to have_received(:write).with(mbox_formatted_message)
92
+ end
93
+
94
+ it "saves the uid to the imap file" do
95
+ subject.add(message_uid, "The\nemail\n")
96
+
97
+ expect(imap_file).to have_received(:write).with(updated_imap_content)
98
+ end
99
+
100
+ context "when the message causes parsing errors" do
101
+ before do
102
+ allow(message).to receive(:to_serialized).and_raise(ArgumentError)
103
+ end
104
+
105
+ it "skips the message" do
106
+ subject.add(message_uid, "The\nemail\n")
107
+ expect(mbox_file).to_not have_received(:write)
108
+ end
109
+
110
+ it "does not fail" do
111
+ expect do
112
+ subject.add(message_uid, "The\nemail\n")
113
+ end.to_not raise_error
114
+ end
115
+ end
116
+ end
117
+ end