imap-backup 1.2.2 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -0
- data/Gemfile +1 -1
- data/Rakefile +4 -4
- data/bin/imap-backup +23 -25
- data/imap-backup.gemspec +14 -14
- data/lib/email/mboxrd/message.rb +23 -5
- data/lib/email/provider.rb +4 -4
- data/lib/imap/backup.rb +18 -18
- data/lib/imap/backup/account/connection.rb +6 -6
- data/lib/imap/backup/account/folder.rb +4 -5
- data/lib/imap/backup/configuration/account.rb +20 -22
- data/lib/imap/backup/configuration/asker.rb +8 -10
- data/lib/imap/backup/configuration/connection_tester.rb +3 -5
- data/lib/imap/backup/configuration/folder_chooser.rb +10 -12
- data/lib/imap/backup/configuration/list.rb +1 -3
- data/lib/imap/backup/configuration/setup.rb +13 -14
- data/lib/imap/backup/configuration/store.rb +7 -8
- data/lib/imap/backup/downloader.rb +0 -2
- data/lib/imap/backup/serializer/base.rb +0 -2
- data/lib/imap/backup/serializer/directory.rb +3 -4
- data/lib/imap/backup/serializer/mbox.rb +11 -12
- data/lib/imap/backup/utils.rb +2 -3
- data/lib/imap/backup/version.rb +2 -2
- data/spec/spec_helper.rb +6 -6
- data/spec/support/higline_test_helpers.rb +1 -1
- data/spec/support/shared_examples/account_flagging.rb +6 -6
- data/spec/unit/account/connection_spec.rb +50 -51
- data/spec/unit/account/folder_spec.rb +18 -19
- data/spec/unit/configuration/account_spec.rb +96 -97
- data/spec/unit/configuration/asker_spec.rb +33 -34
- data/spec/unit/configuration/connection_tester_spec.rb +18 -19
- data/spec/unit/configuration/folder_chooser_spec.rb +34 -35
- data/spec/unit/configuration/list_spec.rb +13 -14
- data/spec/unit/configuration/setup_spec.rb +46 -47
- data/spec/unit/configuration/store_spec.rb +56 -57
- data/spec/unit/downloader_spec.rb +18 -19
- data/spec/unit/email/mboxrd/message_spec.rb +55 -11
- data/spec/unit/email/provider_spec.rb +12 -12
- data/spec/unit/serializer/base_spec.rb +7 -9
- data/spec/unit/serializer/directory_spec.rb +18 -19
- data/spec/unit/serializer/mbox_spec.rb +35 -37
- data/spec/unit/utils_spec.rb +26 -27
- metadata +17 -2
@@ -1,19 +1,18 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
require 'json'
|
1
|
+
require "spec_helper"
|
2
|
+
require "json"
|
4
3
|
|
5
4
|
describe Imap::Backup::Configuration::Store do
|
6
|
-
let(:directory) {
|
7
|
-
let(:file_path) { File.join(directory,
|
5
|
+
let(:directory) { "/base/path" }
|
6
|
+
let(:file_path) { File.join(directory, "/config.json") }
|
8
7
|
let(:file_exists) { true }
|
9
8
|
let(:directory_exists) { true }
|
10
|
-
let(:data) { {:
|
9
|
+
let(:data) { {debug: debug, accounts: accounts} }
|
11
10
|
let(:debug) { true }
|
12
11
|
let(:accounts) { [] }
|
13
12
|
let(:configuration) { data.to_json }
|
14
13
|
|
15
14
|
before do
|
16
|
-
stub_const(
|
15
|
+
stub_const("Imap::Backup::Configuration::Store::CONFIGURATION_DIRECTORY", directory)
|
17
16
|
allow(File).to receive(:directory?).with(directory).and_return(directory_exists)
|
18
17
|
allow(File).to receive(:exist?).with(file_path).and_return(file_exists)
|
19
18
|
allow(Imap::Backup::Utils).to receive(:stat).with(directory).and_return(0700)
|
@@ -22,9 +21,9 @@ describe Imap::Backup::Configuration::Store do
|
|
22
21
|
allow(File).to receive(:read).with(file_path).and_return(configuration)
|
23
22
|
end
|
24
23
|
|
25
|
-
describe
|
24
|
+
describe ".exist?" do
|
26
25
|
[true, false].each do |exists|
|
27
|
-
state = exists ?
|
26
|
+
state = exists ? "exists" : "doesn't exist"
|
28
27
|
context "when the file #{state}" do
|
29
28
|
let(:file_exists) { exists }
|
30
29
|
|
@@ -35,123 +34,123 @@ describe Imap::Backup::Configuration::Store do
|
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
38
|
-
describe
|
39
|
-
it
|
37
|
+
describe "#path" do
|
38
|
+
it "is the directory containing the configuration file" do
|
40
39
|
expect(subject.path).to eq(directory)
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
44
|
-
describe
|
43
|
+
describe "#modified?" do
|
45
44
|
context "'with accounts flagged 'modified'" do
|
46
|
-
let(:accounts) { [{:
|
45
|
+
let(:accounts) { [{name: "foo", modified: true}] }
|
47
46
|
|
48
|
-
it
|
47
|
+
it "is true" do
|
49
48
|
expect(subject.modified?).to be_truthy
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
52
|
context "'with accounts flagged 'delete'" do
|
54
|
-
let(:accounts) { [{:
|
53
|
+
let(:accounts) { [{name: "foo", delete: true}] }
|
55
54
|
|
56
|
-
it
|
55
|
+
it "is true" do
|
57
56
|
expect(subject.modified?).to be_truthy
|
58
57
|
end
|
59
58
|
end
|
60
59
|
|
61
60
|
context "without accounts flagged 'modified'" do
|
62
|
-
let(:accounts) { [{:
|
61
|
+
let(:accounts) { [{name: "foo"}] }
|
63
62
|
|
64
|
-
it
|
63
|
+
it "is false" do
|
65
64
|
expect(subject.modified?).to be_falsey
|
66
65
|
end
|
67
66
|
end
|
68
67
|
end
|
69
68
|
|
70
|
-
describe
|
71
|
-
context
|
72
|
-
it
|
69
|
+
describe "#debug?" do
|
70
|
+
context "when the debug flag is true" do
|
71
|
+
it "is true" do
|
73
72
|
expect(subject.debug?).to be_truthy
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
77
|
-
context
|
76
|
+
context "when the debug flag is false" do
|
78
77
|
let(:debug) { false }
|
79
78
|
|
80
|
-
it
|
79
|
+
it "is false" do
|
81
80
|
expect(subject.debug?).to be_falsey
|
82
81
|
end
|
83
82
|
end
|
84
83
|
|
85
|
-
context
|
86
|
-
let(:data) { {:
|
84
|
+
context "when the debug flag is missing" do
|
85
|
+
let(:data) { {accounts: accounts} }
|
87
86
|
|
88
|
-
it
|
87
|
+
it "is false" do
|
89
88
|
expect(subject.debug?).to be_falsey
|
90
89
|
end
|
91
90
|
end
|
92
91
|
|
93
|
-
context
|
94
|
-
let(:debug) {
|
92
|
+
context "when the debug flag is neither true nor false" do
|
93
|
+
let(:debug) { "hi" }
|
95
94
|
|
96
|
-
it
|
95
|
+
it "is false" do
|
97
96
|
expect(subject.debug?).to be_falsey
|
98
97
|
end
|
99
98
|
end
|
100
99
|
end
|
101
100
|
|
102
|
-
describe
|
101
|
+
describe "#debug=" do
|
103
102
|
before { subject.debug = debug }
|
104
103
|
|
105
|
-
context
|
106
|
-
it
|
104
|
+
context "when the supplied value is true" do
|
105
|
+
it "sets the flag to true" do
|
107
106
|
expect(subject.debug?).to be_truthy
|
108
107
|
end
|
109
108
|
end
|
110
109
|
|
111
|
-
context
|
110
|
+
context "when the supplied value is false" do
|
112
111
|
let(:debug) { false }
|
113
112
|
|
114
|
-
it
|
113
|
+
it "sets the flag to false" do
|
115
114
|
expect(subject.debug?).to be_falsey
|
116
115
|
end
|
117
116
|
end
|
118
117
|
|
119
|
-
context
|
120
|
-
let(:debug) {
|
118
|
+
context "when the supplied value is neither true nor false" do
|
119
|
+
let(:debug) { "ciao" }
|
121
120
|
|
122
|
-
it
|
121
|
+
it "sets the flag to false" do
|
123
122
|
expect(subject.debug?).to be_falsey
|
124
123
|
end
|
125
124
|
end
|
126
125
|
end
|
127
126
|
|
128
|
-
describe
|
127
|
+
describe "#save" do
|
129
128
|
let(:directory_exists) { false }
|
130
|
-
let(:file) { double(
|
129
|
+
let(:file) { double("File", write: nil) }
|
131
130
|
|
132
131
|
before do
|
133
132
|
allow(FileUtils).to receive(:mkdir)
|
134
133
|
allow(FileUtils).to receive(:chmod)
|
135
|
-
allow(File).to receive(:open).with(file_path,
|
136
|
-
allow(JSON).to receive(:pretty_generate).and_return(
|
134
|
+
allow(File).to receive(:open).with(file_path, "w") { |&b| b.call file }
|
135
|
+
allow(JSON).to receive(:pretty_generate).and_return("JSON output")
|
137
136
|
end
|
138
137
|
|
139
138
|
subject { described_class.new }
|
140
139
|
|
141
|
-
it
|
140
|
+
it "creates the config directory" do
|
142
141
|
subject.save
|
143
142
|
|
144
143
|
expect(FileUtils).to have_received(:mkdir).with(directory)
|
145
144
|
end
|
146
145
|
|
147
|
-
it
|
146
|
+
it "saves the configuration" do
|
148
147
|
subject.save
|
149
148
|
|
150
|
-
expect(file).to have_received(:write).with(
|
149
|
+
expect(file).to have_received(:write).with("JSON output")
|
151
150
|
end
|
152
151
|
|
153
|
-
context
|
154
|
-
let(:accounts) { [{:
|
152
|
+
context "when accounts are modified" do
|
153
|
+
let(:accounts) { [{name: "foo", modified: true}] }
|
155
154
|
|
156
155
|
before { subject.save }
|
157
156
|
|
@@ -163,17 +162,17 @@ describe Imap::Backup::Configuration::Store do
|
|
163
162
|
end
|
164
163
|
end
|
165
164
|
|
166
|
-
context
|
165
|
+
context "when accounts are to be deleted" do
|
167
166
|
let(:accounts) do
|
168
167
|
[
|
169
|
-
{:
|
170
|
-
{:
|
168
|
+
{name: "keep_me"},
|
169
|
+
{name: "delete_me", delete: true}
|
171
170
|
]
|
172
171
|
end
|
173
172
|
|
174
173
|
before { subject.save }
|
175
174
|
|
176
|
-
it
|
175
|
+
it "does not save them" do
|
177
176
|
expected = Marshal.load(Marshal.dump(data))
|
178
177
|
expected[:accounts].pop
|
179
178
|
|
@@ -181,15 +180,15 @@ describe Imap::Backup::Configuration::Store do
|
|
181
180
|
end
|
182
181
|
end
|
183
182
|
|
184
|
-
context
|
183
|
+
context "when file permissions are too open" do
|
185
184
|
before { subject.save }
|
186
185
|
|
187
|
-
it
|
186
|
+
it "sets them to 0600" do
|
188
187
|
expect(FileUtils).to have_received(:chmod).with(0600, file_path)
|
189
188
|
end
|
190
189
|
end
|
191
190
|
|
192
|
-
context
|
191
|
+
context "if the configuration file is missing" do
|
193
192
|
let(:file_exists) { false }
|
194
193
|
|
195
194
|
it "doesn't fail" do
|
@@ -199,17 +198,17 @@ describe Imap::Backup::Configuration::Store do
|
|
199
198
|
end
|
200
199
|
end
|
201
200
|
|
202
|
-
context
|
201
|
+
context "if the config file permissions are too lax" do
|
203
202
|
let(:file_exists) { true }
|
204
203
|
|
205
204
|
before do
|
206
|
-
allow(Imap::Backup::Utils).to receive(:check_permissions).with(file_path, 0600).and_raise(
|
205
|
+
allow(Imap::Backup::Utils).to receive(:check_permissions).with(file_path, 0600).and_raise("Error")
|
207
206
|
end
|
208
207
|
|
209
|
-
it
|
208
|
+
it "fails" do
|
210
209
|
expect do
|
211
210
|
subject.save
|
212
|
-
end.to raise_error(RuntimeError,
|
211
|
+
end.to raise_error(RuntimeError, "Error")
|
213
212
|
end
|
214
213
|
end
|
215
214
|
end
|
@@ -1,39 +1,38 @@
|
|
1
|
-
|
2
|
-
require 'spec_helper'
|
1
|
+
require "spec_helper"
|
3
2
|
|
4
3
|
describe Imap::Backup::Downloader do
|
5
|
-
describe
|
6
|
-
let(:message) { {
|
7
|
-
let(:folder) { double(
|
8
|
-
let(:folder_uids) { [
|
9
|
-
let(:serializer) { double(
|
10
|
-
let(:serializer_uids) { [
|
4
|
+
describe "#run" do
|
5
|
+
let(:message) { {"RFC822" => "blah"} }
|
6
|
+
let(:folder) { double("Imap::Backup::Account::Folder", fetch: message, name: "folder") }
|
7
|
+
let(:folder_uids) { ["111", "222", "333"] }
|
8
|
+
let(:serializer) { double("Imap::Backup::Serializer", save: nil) }
|
9
|
+
let(:serializer_uids) { ["222"] }
|
11
10
|
|
12
11
|
subject { described_class.new(folder, serializer) }
|
13
12
|
|
14
13
|
before do
|
15
14
|
allow(folder).to receive(:uids).and_return(folder_uids)
|
16
15
|
allow(serializer).to receive(:uids).and_return(serializer_uids)
|
17
|
-
allow(folder).to receive(:fetch).with(
|
16
|
+
allow(folder).to receive(:fetch).with("333").and_return(nil)
|
18
17
|
subject.run
|
19
18
|
end
|
20
19
|
|
21
|
-
context
|
22
|
-
context
|
23
|
-
it
|
24
|
-
expect(serializer).to have_received(:save).with(
|
20
|
+
context "#run" do
|
21
|
+
context "fetched messages" do
|
22
|
+
it "are saved" do
|
23
|
+
expect(serializer).to have_received(:save).with("111", message)
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
|
-
context
|
29
|
-
specify
|
30
|
-
expect(serializer).to_not have_received(:save).with(
|
27
|
+
context "messages which are already present" do
|
28
|
+
specify "are skipped" do
|
29
|
+
expect(serializer).to_not have_received(:save).with("222", anything)
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
34
|
-
context
|
35
|
-
specify
|
36
|
-
expect(serializer).to_not have_received(:save).with(
|
33
|
+
context "failed fetches" do
|
34
|
+
specify "are skipped" do
|
35
|
+
expect(serializer).to_not have_received(:save).with("333", anything)
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
@@ -1,32 +1,47 @@
|
|
1
|
-
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
|
-
|
3
|
+
msg_no_from = %Q|Delivered-To: you@example.com
|
4
|
+
From: example <www.example.com>
|
5
|
+
To: FirstName LastName <you@example.com>
|
6
|
+
Subject: Re: no subject|
|
7
|
+
|
8
|
+
msg_bad_from = %Q|Delivered-To: you@example.com
|
9
|
+
from: "FirstName LastName (TEXT)" <"TEXT*" <no-reply@example.com>>
|
10
|
+
To: FirstName LastName <you@example.com>
|
11
|
+
Subject: Re: no subject
|
12
|
+
Sender: FistName LastName <"TEXT*"no-reply=example.com@example.com>|
|
13
|
+
|
14
|
+
msg_no_from_but_return_path = %Q|Delivered-To: you@example.com
|
15
|
+
From: example <www.example.com>
|
16
|
+
To: FirstName LastName <you@example.com>
|
17
|
+
Return-Path: <me@example.com>
|
18
|
+
Subject: Re: no subject|
|
4
19
|
|
5
20
|
describe Email::Mboxrd::Message do
|
6
|
-
let(:from) {
|
21
|
+
let(:from) { "me@example.com" }
|
7
22
|
let(:date) { DateTime.new(2012, 12, 13, 18, 23, 45) }
|
8
23
|
let(:message_body) do
|
9
|
-
double(
|
24
|
+
double("Body", clone: cloned_message_body, force_encoding: nil)
|
10
25
|
end
|
11
26
|
let(:cloned_message_body) { "Foo\nBar\nFrom at the beginning of the line\n>>From quoted" }
|
12
27
|
|
13
28
|
subject { described_class.new(message_body) }
|
14
29
|
|
15
|
-
context
|
16
|
-
let(:mail) { double(
|
30
|
+
context "#to_s" do
|
31
|
+
let(:mail) { double("Mail", from: [from], date: date) }
|
17
32
|
|
18
33
|
before do
|
19
34
|
allow(Mail).to receive(:new).with(cloned_message_body).and_return(mail)
|
20
35
|
end
|
21
36
|
|
22
|
-
it
|
37
|
+
it "does not modify the message" do
|
23
38
|
subject.to_s
|
24
39
|
|
25
|
-
expect(message_body).to_not have_received(:force_encoding).with(
|
40
|
+
expect(message_body).to_not have_received(:force_encoding).with("binary")
|
26
41
|
end
|
27
42
|
|
28
43
|
it "adds a 'From ' line at the start" do
|
29
|
-
expect(subject.to_s).to start_with(
|
44
|
+
expect(subject.to_s).to start_with("From " + from + " " + date.asctime + "\n")
|
30
45
|
end
|
31
46
|
|
32
47
|
it "replaces existing 'From ' with '>From '" do
|
@@ -37,12 +52,41 @@ describe Email::Mboxrd::Message do
|
|
37
52
|
expect(subject.to_s).to include("\n>>>From quoted")
|
38
53
|
end
|
39
54
|
|
40
|
-
context
|
55
|
+
context "when date is missing" do
|
41
56
|
let(:date) { nil }
|
42
57
|
|
43
|
-
it
|
58
|
+
it "does no fail" do
|
44
59
|
expect { subject.to_s }.to_not raise_error
|
45
60
|
end
|
46
61
|
end
|
47
62
|
end
|
63
|
+
|
64
|
+
context '#from' do
|
65
|
+
before do
|
66
|
+
# call original for these tests because we want to test the behaviour of
|
67
|
+
# class-under-test given different behaviour of the Mail parser
|
68
|
+
allow(Mail).to receive(:new).and_call_original
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when original message 'from' is nil" do
|
72
|
+
let(:message_body) { msg_no_from }
|
73
|
+
it "'from' is empty string" do
|
74
|
+
expect(subject.to_s).to start_with("From \n")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when original message 'from' is a string but not an address" do
|
79
|
+
let(:message_body) { msg_bad_from }
|
80
|
+
it "'from' is empty string" do
|
81
|
+
expect(subject.to_s).to start_with("From \n")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when original message 'from' is nil and 'envelope from' is nil and 'return path' is available" do
|
86
|
+
let(:message_body) { msg_no_from_but_return_path }
|
87
|
+
it "'return path' is used as 'from'" do
|
88
|
+
expect(subject.to_s).to start_with("From " + from + " \n")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
48
92
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe Email::Provider do
|
4
|
-
describe
|
5
|
-
context
|
6
|
-
[[
|
4
|
+
describe ".for_address" do
|
5
|
+
context "known providers" do
|
6
|
+
[["gmail.com", :gmail], ["fastmail.fm", :fastmail]].each do |domain, provider|
|
7
7
|
it "recognizes #{provider}" do
|
8
8
|
address = "foo@#{domain}"
|
9
9
|
expect(described_class.for_address(address).provider).to eq(provider)
|
@@ -11,24 +11,24 @@ describe Email::Provider do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
context
|
15
|
-
it
|
16
|
-
expect(described_class.for_address(
|
14
|
+
context "with unknown providers" do
|
15
|
+
it "returns a default provider" do
|
16
|
+
expect(described_class.for_address("foo@unknown.com").provider).to eq(:default)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
subject { described_class.new(:gmail) }
|
22
22
|
|
23
|
-
describe
|
24
|
-
it
|
23
|
+
describe "#options" do
|
24
|
+
it "returns options" do
|
25
25
|
expect(subject.options).to eq(port: 993, ssl: true)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
describe
|
30
|
-
it
|
31
|
-
expect(subject.host).to eq(
|
29
|
+
describe "#host" do
|
30
|
+
it "returns host" do
|
31
|
+
expect(subject.host).to eq("imap.gmail.com")
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|