imap-backup 1.2.2 → 1.2.3
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.
- 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
|