imap-backup 5.0.0 → 6.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -7
  3. data/bin/imap-backup +4 -0
  4. data/docs/development.md +10 -4
  5. data/imap-backup.gemspec +2 -7
  6. data/lib/cli_coverage.rb +18 -0
  7. data/lib/imap/backup/account/connection.rb +7 -11
  8. data/lib/imap/backup/account/folder.rb +0 -16
  9. data/lib/imap/backup/account.rb +31 -11
  10. data/lib/imap/backup/cli/folders.rb +3 -3
  11. data/lib/imap/backup/cli/migrate.rb +3 -3
  12. data/lib/imap/backup/cli/restore.rb +20 -4
  13. data/lib/imap/backup/cli/utils.rb +2 -2
  14. data/lib/imap/backup/cli.rb +6 -7
  15. data/lib/imap/backup/configuration.rb +1 -11
  16. data/lib/imap/backup/downloader.rb +13 -9
  17. data/lib/imap/backup/serializer/directory.rb +37 -0
  18. data/lib/imap/backup/serializer/imap.rb +120 -0
  19. data/lib/imap/backup/serializer/mbox.rb +23 -94
  20. data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
  21. data/lib/imap/backup/serializer.rb +180 -3
  22. data/lib/imap/backup/setup/account.rb +52 -29
  23. data/lib/imap/backup/setup/helpers.rb +1 -1
  24. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
  25. data/lib/imap/backup/version.rb +2 -2
  26. data/lib/imap/backup.rb +0 -1
  27. data/spec/features/backup_spec.rb +22 -29
  28. data/spec/features/restore_spec.rb +8 -6
  29. data/spec/features/support/aruba.rb +12 -3
  30. data/spec/features/support/backup_directory.rb +0 -4
  31. data/spec/features/support/email_server.rb +0 -1
  32. data/spec/spec_helper.rb +4 -9
  33. data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
  34. data/spec/unit/imap/backup/account/folder_spec.rb +18 -16
  35. data/spec/unit/imap/backup/account_spec.rb +246 -0
  36. data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
  37. data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
  38. data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
  39. data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
  40. data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
  41. data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
  42. data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
  43. data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
  44. data/spec/unit/imap/backup/cli_spec.rb +93 -0
  45. data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
  46. data/spec/unit/imap/backup/configuration_spec.rb +2 -2
  47. data/spec/unit/imap/backup/downloader_spec.rb +60 -8
  48. data/spec/unit/imap/backup/logger_spec.rb +1 -1
  49. data/spec/unit/imap/backup/migrator_spec.rb +1 -1
  50. data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
  51. data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
  52. data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
  53. data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
  54. data/spec/unit/imap/backup/serializer_spec.rb +296 -0
  55. data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
  56. data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
  57. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
  58. data/spec/unit/imap/backup/uploader_spec.rb +1 -1
  59. data/spec/unit/retry_on_error_spec.rb +34 -0
  60. metadata +44 -37
  61. data/lib/imap/backup/serializer/mbox_store.rb +0 -217
  62. data/lib/thunderbird/install.rb +0 -16
  63. data/lib/thunderbird/local_folder.rb +0 -65
  64. data/lib/thunderbird/profile.rb +0 -30
  65. data/lib/thunderbird/profiles.rb +0 -71
  66. data/lib/thunderbird/subdirectory.rb +0 -93
  67. data/lib/thunderbird/subdirectory_placeholder.rb +0 -21
  68. data/lib/thunderbird.rb +0 -14
  69. data/spec/gather_rspec_coverage.rb +0 -1
  70. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
@@ -1,28 +1,28 @@
1
1
  require "features/helper"
2
2
 
3
- RSpec.describe "backup", type: :feature, docker: true do
3
+ RSpec.describe "backup", type: :aruba, docker: true do
4
4
  include_context "imap-backup connection"
5
5
  include_context "message-fixtures"
6
6
 
7
+ let(:local_backup_path) { File.expand_path("~/backup") }
8
+ let(:backup_folders) { [{name: folder}] }
9
+ let(:folder) { "my-stuff" }
7
10
  let(:messages_as_mbox) do
8
11
  message_as_mbox_entry(msg1) + message_as_mbox_entry(msg2)
9
12
  end
10
- let(:folder) { "my-stuff" }
11
- let(:email1) { send_email folder, msg1 }
12
- let(:email2) { send_email folder, msg2 }
13
+
13
14
  let!(:pre) {}
14
15
  let!(:setup) do
15
16
  server_create_folder folder
16
- email1
17
- email2
18
- connection.run_backup
17
+ send_email folder, msg1
18
+ send_email folder, msg2
19
+ create_config(accounts: [account.to_h])
20
+
21
+ run_command_and_stop("imap-backup backup")
19
22
  end
20
23
 
21
24
  after do
22
- FileUtils.rm_rf local_backup_path
23
- delete_emails folder
24
25
  server_delete_folder folder
25
- connection.disconnect
26
26
  end
27
27
 
28
28
  it "downloads messages" do
@@ -51,17 +51,16 @@ RSpec.describe "backup", type: :feature, docker: true do
51
51
 
52
52
  context "when uid_validity does not match" do
53
53
  let(:new_name) { "NEWNAME" }
54
- let(:email3) { send_email folder, msg3 }
55
54
  let(:original_folder_uid_validity) { server_uid_validity(folder) }
56
55
  let!(:pre) do
57
56
  server_create_folder folder
58
- email3
57
+ send_email folder, msg3
59
58
  original_folder_uid_validity
60
59
  connection.run_backup
61
60
  connection.disconnect
62
61
  server_rename_folder folder, new_name
63
62
  end
64
- let(:renamed_folder) { "#{folder}.#{original_folder_uid_validity}" }
63
+ let(:renamed_folder) { "#{folder}-#{original_folder_uid_validity}" }
65
64
 
66
65
  after do
67
66
  server_delete_folder new_name
@@ -71,37 +70,31 @@ RSpec.describe "backup", type: :feature, docker: true do
71
70
  expect(mbox_content(renamed_folder)).to eq(message_as_mbox_entry(msg3))
72
71
  end
73
72
 
73
+ it "renames the old metadata file" do
74
+ expect(imap_parsed(renamed_folder)).to be_a Hash
75
+ end
76
+
74
77
  it "downloads messages" do
75
78
  expect(mbox_content(folder)).to eq(messages_as_mbox)
76
79
  end
77
80
 
81
+ it "creates a metadata file" do
82
+ expect(imap_parsed(folder)).to be_a Hash
83
+ end
84
+
78
85
  context "when a renamed local backup exists" do
79
86
  let!(:pre) do
80
87
  super()
88
+ create_directory local_backup_path
81
89
  File.write(imap_path(renamed_folder), "existing imap")
82
90
  File.write(mbox_path(renamed_folder), "existing mbox")
83
91
  end
84
92
 
85
93
  it "moves the old backup to a uniquely named directory" do
86
- renamed = "#{folder}.#{original_folder_uid_validity}.1"
94
+ renamed = "#{folder}-#{original_folder_uid_validity}-1"
87
95
  expect(mbox_content(renamed)).to eq(message_as_mbox_entry(msg3))
88
96
  end
89
97
  end
90
98
  end
91
-
92
- context "when an unversioned .imap file is found" do
93
- let!(:pre) do
94
- File.open(imap_path(folder), "w") { |f| f.write "old format imap" }
95
- File.open(mbox_path(folder), "w") { |f| f.write "old format emails" }
96
- end
97
-
98
- it "replaces the .imap file with a versioned JSON file" do
99
- expect(imap_metadata[:uids]).to eq(folder_uids)
100
- end
101
-
102
- it "does the download" do
103
- expect(mbox_content(folder)).to eq(messages_as_mbox)
104
- end
105
- end
106
99
  end
107
100
  end
@@ -1,9 +1,11 @@
1
1
  require "features/helper"
2
2
 
3
- RSpec.describe "restore", type: :feature, docker: true do
3
+ RSpec.describe "restore", type: :aruba, docker: true do
4
4
  include_context "imap-backup connection"
5
5
  include_context "message-fixtures"
6
6
 
7
+ let(:local_backup_path) { File.expand_path("~/backup") }
8
+ let(:folder) { "my-stuff" }
7
9
  let(:messages_as_mbox) do
8
10
  message_as_mbox_entry(msg1) + message_as_mbox_entry(msg2)
9
11
  end
@@ -12,19 +14,19 @@ RSpec.describe "restore", type: :feature, docker: true do
12
14
  end
13
15
  let(:message_uids) { [msg1[:uid], msg2[:uid]] }
14
16
  let(:existing_imap_content) { imap_data(uid_validity, message_uids).to_json }
15
- let(:folder) { "my-stuff" }
16
17
  let(:uid_validity) { 1234 }
17
18
 
18
19
  let!(:pre) {}
19
20
  let!(:setup) do
21
+ create_directory local_backup_path
20
22
  File.write(imap_path(folder), existing_imap_content)
21
23
  File.write(mbox_path(folder), messages_as_mbox)
22
- connection.restore
24
+ create_config(accounts: [account.to_h])
25
+
26
+ run_command_and_stop("imap-backup restore #{account.username}")
23
27
  end
24
28
  let(:cleanup) do
25
- FileUtils.rm_rf local_backup_path
26
29
  server_delete_folder folder
27
- connection.disconnect
28
30
  end
29
31
 
30
32
  after { cleanup }
@@ -92,7 +94,7 @@ RSpec.describe "restore", type: :feature, docker: true do
92
94
  end
93
95
 
94
96
  context "when the folder has content" do
95
- let(:new_folder) { "#{folder}.#{uid_validity}" }
97
+ let(:new_folder) { "#{folder}-#{uid_validity}" }
96
98
  let(:cleanup) do
97
99
  server_delete_folder new_folder
98
100
  super()
@@ -1,7 +1,11 @@
1
1
  require "aruba/rspec"
2
2
 
3
+ require_relative "backup_directory"
4
+ require "imap/backup/serializer/mbox"
5
+
3
6
  Aruba.configure do |config|
4
7
  config.home_directory = File.expand_path("./tmp/home")
8
+ config.allow_absolute_paths = true
5
9
  end
6
10
 
7
11
  module ConfigurationHelpers
@@ -33,10 +37,10 @@ module StoreHelpers
33
37
  account = config.accounts.find { |a| a.username == email }
34
38
  raise "Account not found" if !account
35
39
  FileUtils.mkdir_p account.local_path
36
- store = Imap::Backup::Serializer::MboxStore.new(account.local_path, folder)
37
- store.uid_validity = "42" if !store.uid_validity
40
+ serializer = Imap::Backup::Serializer.new(account.local_path, folder)
41
+ serializer.force_uid_validity("42") if !serializer.uid_validity
38
42
  serialized = to_serialized(from: from, subject: subject, body: body)
39
- store.add(uid, serialized)
43
+ serializer.append uid, serialized
40
44
  end
41
45
 
42
46
  def to_serialized(from:, subject:, body:)
@@ -58,11 +62,16 @@ end
58
62
  RSpec.configure do |config|
59
63
  config.include ConfigurationHelpers, type: :aruba
60
64
  config.include StoreHelpers, type: :aruba
65
+ config.include BackupDirectoryHelpers, type: :aruba
61
66
 
62
67
  config.before(:suite) do
63
68
  FileUtils.rm_rf "./tmp/home"
64
69
  end
65
70
 
71
+ config.before(:example, type: :aruba) do
72
+ set_environment_variable("COVERAGE", "aruba")
73
+ end
74
+
66
75
  config.after do
67
76
  FileUtils.rm_rf "./tmp/home"
68
77
  end
@@ -41,7 +41,3 @@ module BackupDirectoryHelpers
41
41
  JSON.parse(imap_content(name), symbolize_names: true)
42
42
  end
43
43
  end
44
-
45
- RSpec.configure do |config|
46
- config.include BackupDirectoryHelpers, type: :feature
47
- end
@@ -87,7 +87,6 @@ module EmailServerHelpers
87
87
  imap.delete folder
88
88
  imap.disconnect
89
89
  rescue StandardError => e
90
- puts e.to_s
91
90
  ensure
92
91
  @imap = nil
93
92
  end
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,13 @@
1
- require "codeclimate-test-reporter"
2
1
  require "rspec"
3
2
 
4
- CodeClimate::TestReporter.start
3
+ $LOAD_PATH << File.expand_path("../lib", __dir__)
5
4
 
6
- spec_path = File.dirname(__FILE__)
7
- $LOAD_PATH << File.expand_path("../lib", spec_path)
8
-
9
- support_glob = File.join(spec_path, "support", "**", "*.rb")
5
+ support_glob = File.join(__dir__, "support", "**", "*.rb")
10
6
  Dir[support_glob].sort.each { |f| require f }
11
7
 
12
8
  require "simplecov"
13
- SimpleCov.start do
14
- add_filter "/spec/"
15
- end
9
+
10
+ SimpleCov.command_name "RSpec tests"
16
11
 
17
12
  require "imap/backup"
18
13
  require "imap/backup/cli"
@@ -24,21 +24,24 @@ describe Imap::Backup::Account::Connection do
24
24
  let(:account) do
25
25
  instance_double(
26
26
  Imap::Backup::Account,
27
- username: USERNAME,
27
+ username: username,
28
28
  password: PASSWORD,
29
29
  local_path: LOCAL_PATH,
30
30
  folders: config_folders,
31
+ multi_fetch_size: multi_fetch_size,
31
32
  server: server,
32
33
  connection_options: nil
33
34
  )
34
35
  end
36
+ let(:username) { USERNAME }
35
37
  let(:config_folders) { [FOLDER_CONFIG] }
38
+ let(:multi_fetch_size) { 1 }
36
39
  let(:root_info) do
37
40
  instance_double(Net::IMAP::MailboxList, name: ROOT_NAME)
38
41
  end
39
42
  let(:serializer) do
40
43
  instance_double(
41
- Imap::Backup::Serializer::Mbox,
44
+ Imap::Backup::Serializer,
42
45
  folder: serialized_folder,
43
46
  force_uid_validity: nil,
44
47
  apply_uid_validity: new_uid_validity,
@@ -87,6 +90,23 @@ describe Imap::Backup::Account::Connection do
87
90
  end
88
91
  end
89
92
 
93
+ context "when the provider is Apple" do
94
+ let(:username) { "user@mac.com" }
95
+ let(:apple_client) do
96
+ instance_double(
97
+ Imap::Backup::Client::AppleMail, login: nil
98
+ )
99
+ end
100
+
101
+ before do
102
+ allow(Imap::Backup::Client::AppleMail).to receive(:new) { apple_client }
103
+ end
104
+
105
+ it "returns the Apple client" do
106
+ expect(result).to eq(apple_client)
107
+ end
108
+ end
109
+
90
110
  context "when run" do
91
111
  before { subject.client }
92
112
 
@@ -116,7 +136,7 @@ describe Imap::Backup::Account::Connection do
116
136
 
117
137
  before do
118
138
  allow(Imap::Backup::Account::Folder).to receive(:new) { folder }
119
- allow(Imap::Backup::Serializer::Mbox).to receive(:new) { serializer }
139
+ allow(Imap::Backup::Serializer).to receive(:new) { serializer }
120
140
  end
121
141
 
122
142
  it "creates the path" do
@@ -150,16 +170,24 @@ describe Imap::Backup::Account::Connection do
150
170
  let(:exists) { true }
151
171
  let(:uid_validity) { 123 }
152
172
  let(:downloader) { instance_double(Imap::Backup::Downloader, run: nil) }
173
+ let(:multi_fetch_size) { 10 }
153
174
 
154
175
  before do
155
176
  allow(Imap::Backup::Downloader).
156
177
  to receive(:new).with(folder, serializer, anything) { downloader }
157
178
  allow(Imap::Backup::Account::Folder).to receive(:new).
158
179
  with(subject, BACKUP_FOLDER) { folder }
159
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
180
+ allow(Imap::Backup::Serializer).to receive(:new).
160
181
  with(LOCAL_PATH, IMAP_FOLDER) { serializer }
161
182
  end
162
183
 
184
+ it "passes the multi_fetch_size" do
185
+ subject.run_backup
186
+
187
+ expect(Imap::Backup::Downloader).to have_received(:new).
188
+ with(anything, anything, {multi_fetch_size: 10})
189
+ end
190
+
163
191
  context "with supplied config_folders" do
164
192
  it "runs the downloader" do
165
193
  expect(downloader).to receive(:run)
@@ -184,7 +212,7 @@ describe Imap::Backup::Account::Connection do
184
212
  before do
185
213
  allow(Imap::Backup::Account::Folder).to receive(:new).
186
214
  with(subject, ROOT_NAME) { folder }
187
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
215
+ allow(Imap::Backup::Serializer).to receive(:new).
188
216
  with(LOCAL_PATH, ROOT_NAME) { serializer }
189
217
  end
190
218
 
@@ -273,18 +301,18 @@ describe Imap::Backup::Account::Connection do
273
301
  end
274
302
  let(:updated_serializer) do
275
303
  instance_double(
276
- Imap::Backup::Serializer::Mbox, force_uid_validity: nil
304
+ Imap::Backup::Serializer, force_uid_validity: nil
277
305
  )
278
306
  end
279
307
 
280
308
  before do
281
309
  allow(Imap::Backup::Account::Folder).to receive(:new).
282
310
  with(subject, FOLDER_NAME) { folder }
283
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
311
+ allow(Imap::Backup::Serializer).to receive(:new).
284
312
  with(anything, FOLDER_NAME) { serializer }
285
313
  allow(Imap::Backup::Account::Folder).to receive(:new).
286
314
  with(subject, "new name") { updated_folder }
287
- allow(Imap::Backup::Serializer::Mbox).to receive(:new).
315
+ allow(Imap::Backup::Serializer).to receive(:new).
288
316
  with(anything, "new name") { updated_serializer }
289
317
  allow(Imap::Backup::Uploader).to receive(:new).
290
318
  with(folder, serializer) { uploader }
@@ -64,26 +64,36 @@ describe Imap::Backup::Account::Folder do
64
64
  expect(subject.uids).to eq([])
65
65
  end
66
66
  end
67
+
68
+ context "when the UID search fails" do
69
+ before do
70
+ allow(client).to receive(:uid_search).and_raise(NoMethodError)
71
+ end
72
+
73
+ it "returns an empty array" do
74
+ expect(subject.uids).to eq([])
75
+ end
76
+ end
67
77
  end
68
78
 
69
- describe "#fetch" do
79
+ describe "#fetch_multi" do
70
80
  let(:message_body) { instance_double(String, force_encoding: nil) }
71
- let(:attributes) { {"BODY[]" => message_body, "other" => "xxx"} }
81
+ let(:attributes) { {"UID" => "uid", "BODY[]" => message_body, "other" => "xxx"} }
72
82
  let(:fetch_data_item) do
73
83
  instance_double(Net::IMAP::FetchData, attr: attributes)
74
84
  end
75
85
 
76
86
  before { allow(client).to receive(:uid_fetch) { [fetch_data_item] } }
77
87
 
78
- it "returns the message" do
79
- expect(subject.fetch(123)).to eq(message_body)
88
+ it "returns the uid and message" do
89
+ expect(subject.fetch_multi([123])).to eq([{uid: "uid", body: message_body}])
80
90
  end
81
91
 
82
92
  context "when the server responds with nothing" do
83
93
  before { allow(client).to receive(:uid_fetch) { nil } }
84
94
 
85
95
  it "is nil" do
86
- expect(subject.fetch(123)).to be_nil
96
+ expect(subject.fetch_multi([123])).to be_nil
87
97
  end
88
98
  end
89
99
 
@@ -94,15 +104,7 @@ describe Imap::Backup::Account::Folder do
94
104
  end
95
105
 
96
106
  it "is nil" do
97
- expect(subject.fetch(123)).to be_nil
98
- end
99
- end
100
-
101
- context "when the response doesn't include 'BODY[]'" do
102
- let(:attributes) { {} }
103
-
104
- it "is nil" do
105
- expect(subject.fetch(123)).to be_nil
107
+ expect(subject.fetch_multi([123])).to be_nil
106
108
  end
107
109
  end
108
110
 
@@ -113,13 +115,13 @@ describe Imap::Backup::Account::Folder do
113
115
  end
114
116
 
115
117
  it "retries" do
116
- subject.fetch(123)
118
+ subject.fetch_multi([123])
117
119
 
118
120
  expect(client).to have_received(:uid_fetch).twice
119
121
  end
120
122
 
121
123
  it "succeeds" do
122
- subject.fetch(123)
124
+ subject.fetch_multi([123])
123
125
  end
124
126
  end
125
127
  end
@@ -0,0 +1,246 @@
1
+ module Imap::Backup
2
+ describe Account do
3
+ subject { described_class.new(options) }
4
+
5
+ let(:options) { {username: "user", password: "pwd"} }
6
+
7
+ describe "#changes" do
8
+ it "lists changes" do
9
+ subject.username = "new"
10
+
11
+ expect(subject.changes).to eq(username: {from: "user", to: "new"})
12
+ end
13
+
14
+ context "when more than one change is made" do
15
+ it "retains the last one" do
16
+ subject.username = "first"
17
+ subject.username = "second"
18
+
19
+ expect(subject.changes).to eq(username: {from: "user", to: "second"})
20
+ end
21
+ end
22
+
23
+ context "when a value is reset to its original value" do
24
+ it "removes the change" do
25
+ subject.username = "changed"
26
+ subject.username = "user"
27
+
28
+ expect(subject.changes).to eq({})
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#connection" do
34
+ it "returns a Connection for the Account" do
35
+ result = subject.connection
36
+
37
+ expect(result).to be_a(Account::Connection)
38
+ expect(result.account).to be subject
39
+ end
40
+ end
41
+
42
+ describe "#valid?" do
43
+ context "with username and password" do
44
+ it "is true" do
45
+ expect(subject.valid?).to be true
46
+ end
47
+ end
48
+
49
+ context "without a username" do
50
+ let(:options) { {password: "pwd"} }
51
+
52
+ it "is false" do
53
+ expect(subject.valid?).to be false
54
+ end
55
+ end
56
+
57
+ context "without a password" do
58
+ let(:options) { {username: "user"} }
59
+
60
+ it "is false" do
61
+ expect(subject.valid?).to be false
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "#modified?" do
67
+ context "with changes" do
68
+ it "is true" do
69
+ subject.username = "new"
70
+
71
+ expect(subject.modified?).to be true
72
+ end
73
+ end
74
+
75
+ context "without changes" do
76
+ it "is false" do
77
+ expect(subject.modified?).to be false
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#clear_changes" do
83
+ it "clears changes" do
84
+ subject.username = "new"
85
+ subject.clear_changes
86
+
87
+ expect(subject.changes).to eq({})
88
+ end
89
+ end
90
+
91
+ describe "#mark_for_deletion" do
92
+ it "sets marked_for_deletion" do
93
+ subject.mark_for_deletion
94
+
95
+ expect(subject.marked_for_deletion?).to be true
96
+ end
97
+ end
98
+
99
+ describe "#marked_for_deletion?" do
100
+ it "defaults to false" do
101
+ expect(subject.marked_for_deletion?).to be false
102
+ end
103
+ end
104
+
105
+ describe "#to_h" do
106
+ it "returns a Hash representation" do
107
+ expect(subject.to_h).to eq({username: "user", password: "pwd"})
108
+ end
109
+
110
+ context "when local_path is set" do
111
+ let(:options) { {username: "user", password: "pwd", local_path: "local_path"} }
112
+
113
+ it "includes local_path" do
114
+ expect(subject.to_h).to include({local_path: "local_path"})
115
+ end
116
+ end
117
+
118
+ context "when folders is set" do
119
+ let(:options) { {username: "user", password: "pwd", folders: ["folder"]} }
120
+
121
+ it "includes folders" do
122
+ expect(subject.to_h).to include({folders: ["folder"]})
123
+ end
124
+ end
125
+
126
+ context "when multi_fetch_size is set" do
127
+ let(:options) { {username: "user", password: "pwd", multi_fetch_size: "3"} }
128
+
129
+ it "includes multi_fetch_size" do
130
+ expect(subject.to_h).to include({multi_fetch_size: 3})
131
+ end
132
+ end
133
+
134
+ context "when server is set" do
135
+ let(:options) { {username: "user", password: "pwd", server: "server"} }
136
+
137
+ it "includes server" do
138
+ expect(subject.to_h).to include({server: "server"})
139
+ end
140
+ end
141
+
142
+ context "when connection_options is set" do
143
+ let(:options) { {username: "user", password: "pwd", connection_options: "foo"} }
144
+
145
+ it "includes connection_options" do
146
+ expect(subject.to_h).to include({connection_options: "foo"})
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#multi_fetch_size" do
152
+ let(:options) { {username: "user", password: "pwd", multi_fetch_size: multi_fetch_size} }
153
+ let(:multi_fetch_size) { 10 }
154
+
155
+ it "returns the initialized value" do
156
+ expect(subject.multi_fetch_size).to eq(10)
157
+ end
158
+
159
+ context "when the initialized value is a string representation of a positive number" do
160
+ let(:multi_fetch_size) { "10" }
161
+
162
+ it "returns one" do
163
+ expect(subject.multi_fetch_size).to eq(10)
164
+ end
165
+ end
166
+
167
+ context "when the initialized value is not a number" do
168
+ let(:multi_fetch_size) { "ciao" }
169
+
170
+ it "returns one" do
171
+ expect(subject.multi_fetch_size).to eq(1)
172
+ end
173
+ end
174
+
175
+ context "when the initialized value is not a positive number" do
176
+ let(:multi_fetch_size) { "-99" }
177
+
178
+ it "returns one" do
179
+ expect(subject.multi_fetch_size).to eq(1)
180
+ end
181
+ end
182
+ end
183
+
184
+ [
185
+ [:username, "username", "username"],
186
+ [:password, "password", "password"],
187
+ [:local_path, "local_path", "local_path"],
188
+ [:multi_fetch_size, "2", 2],
189
+ [:server, "server", "server"],
190
+ [:folders, ["folder"], ["folder"]],
191
+ [:connection_options, '{"some": "option"}', {"some" => "option"}]
192
+ ].each do |attribute, value, expected|
193
+ describe "##{attribute}=" do
194
+ let(:options) { {} }
195
+
196
+ before { subject.send(:"#{attribute}=", value) }
197
+
198
+ it "sets the #{attribute}" do
199
+ expect(subject.send(attribute)).to eq(expected)
200
+ end
201
+
202
+ it "adds a change" do
203
+ expect(subject.changes).to eq(attribute => {from: nil, to: expected})
204
+ end
205
+
206
+ if attribute == :folders
207
+ context "when the supplied value is not an Array" do
208
+ it "fails" do
209
+ expect do
210
+ subject.folders = "aaa"
211
+ end.to raise_error(RuntimeError, /must be an Array/)
212
+ end
213
+ end
214
+ end
215
+
216
+ if attribute == :multi_fetch_size
217
+ context "when the supplied value is not a number" do
218
+ before { subject.multi_fetch_size = "ciao" }
219
+
220
+ it "sets multi_fetch_size to one" do
221
+ expect(subject.multi_fetch_size).to eq(1)
222
+ end
223
+ end
224
+
225
+ context "when the supplied value is not a positive number" do
226
+ before { subject.multi_fetch_size = "-1" }
227
+
228
+ it "sets multi_fetch_size to one" do
229
+ expect(subject.multi_fetch_size).to eq(1)
230
+ end
231
+ end
232
+ end
233
+
234
+ if attribute == :connection_options
235
+ context "when the supplied value is not valid JSON" do
236
+ it "fails" do
237
+ expect do
238
+ subject.connection_options = "NOT JSON"
239
+ end.to raise_error(JSON::ParserError)
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end