imap-backup 4.2.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/imap-backup.gemspec +2 -0
  3. data/lib/imap/backup/account/connection.rb +4 -4
  4. data/lib/imap/backup/account/folder.rb +8 -0
  5. data/lib/imap/backup/cli/folders.rb +1 -1
  6. data/lib/imap/backup/cli/helpers.rb +4 -1
  7. data/lib/imap/backup/cli/local.rb +1 -1
  8. data/lib/imap/backup/cli/migrate.rb +114 -0
  9. data/lib/imap/backup/cli/remote.rb +15 -0
  10. data/lib/imap/backup/cli/restore.rb +20 -4
  11. data/lib/imap/backup/cli.rb +55 -10
  12. data/lib/imap/backup/client/default.rb +2 -2
  13. data/lib/imap/backup/downloader.rb +23 -9
  14. data/lib/imap/backup/migrator.rb +51 -0
  15. data/lib/imap/backup/sanitizer.rb +7 -4
  16. data/lib/imap/backup/version.rb +2 -2
  17. data/spec/features/backup_spec.rb +13 -12
  18. data/spec/features/configuration/minimal_configuration.rb +15 -0
  19. data/spec/features/configuration/missing_configuration.rb +14 -0
  20. data/spec/features/folders_spec.rb +36 -0
  21. data/spec/features/local/list_accounts_spec.rb +12 -0
  22. data/spec/features/local/list_emails_spec.rb +21 -0
  23. data/spec/features/local/list_folders_spec.rb +21 -0
  24. data/spec/features/local/show_an_email_spec.rb +34 -0
  25. data/spec/features/migrate_spec.rb +35 -0
  26. data/spec/features/remote/list_account_folders_spec.rb +16 -0
  27. data/spec/features/restore_spec.rb +7 -5
  28. data/spec/features/support/aruba.rb +73 -0
  29. data/spec/features/support/backup_directory.rb +0 -4
  30. data/spec/features/support/email_server.rb +1 -1
  31. data/spec/spec_helper.rb +2 -3
  32. data/spec/unit/imap/backup/account/connection_spec.rb +6 -8
  33. data/spec/unit/imap/backup/account/folder_spec.rb +26 -5
  34. data/spec/unit/imap/backup/cli/helpers_spec.rb +87 -0
  35. data/spec/unit/imap/backup/migrator_spec.rb +58 -0
  36. metadata +57 -11
  37. data/lib/thunderbird/install.rb +0 -16
  38. data/lib/thunderbird/local_folder.rb +0 -65
  39. data/lib/thunderbird/profile.rb +0 -30
  40. data/lib/thunderbird/profiles.rb +0 -71
  41. data/lib/thunderbird/subdirectory.rb +0 -93
  42. data/lib/thunderbird/subdirectory_placeholder.rb +0 -21
  43. data/lib/thunderbird.rb +0 -14
  44. data/spec/gather_rspec_coverage.rb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0cd29188ed972abfb2b2ce21d237ce3b7aed5b434717ba64fa2e3475da38ded
4
- data.tar.gz: 597b58cdee1ab1b5881ea29111ac85e7fdedfbaa370824d0d24ba1b19c869dcb
3
+ metadata.gz: 59babb556b2f47a07590debff15dcd5456f1e61454c3aba2e504610ce4322647
4
+ data.tar.gz: 89909077444746e44fe3955070df6b590942da0d44da58c9b08b282e694000d5
5
5
  SHA512:
6
- metadata.gz: a3adc1984eea76a5ef993a40e89520a01997e6d01357e0a1e41cd4b58a4efc56801fc3d4f8801b95e828ec3a6a1e7884be974aaabf40f921a3faccdc99cf9b98
7
- data.tar.gz: 55c0c91fdfb4829ba77be5292b4e475c6ea31e9e6e9ea5abf9702121f8a12c20b9b0105da5e1b97bcb2482105807c614530dea9e058d64af2cde96f7fd19348b
6
+ metadata.gz: ea00bb55bb853cef73ec12171a4bdde9b0b5ef88a914065bbe307f48712db3329e8c8f1712cfa48b4a49102d20fb38cc465dc463c2d3ea9e0caba000a184766b
7
+ data.tar.gz: 24d368c209c11db4ae7f05a93b9cf4e0435b65eef0cffd7e58f8c669e634ca735aa3187b6a6506b7b10ba3bec961d3aa9d7387df40d4880f1bed73df7f24bbb9
data/imap-backup.gemspec CHANGED
@@ -27,7 +27,9 @@ Gem::Specification.new do |gem|
27
27
  gem.add_runtime_dependency "os"
28
28
  gem.add_runtime_dependency "rake"
29
29
  gem.add_runtime_dependency "thor", "~> 1.1"
30
+ gem.add_runtime_dependency "thunderbird", ">= 0.0.0"
30
31
 
32
+ gem.add_development_dependency "aruba", ">= 0.0.0"
31
33
  gem.add_development_dependency "codeclimate-test-reporter", "~> 0.4.8"
32
34
  if RUBY_ENGINE == "jruby"
33
35
  gem.add_development_dependency "pry-debugger-jruby"
@@ -16,11 +16,8 @@ module Imap::Backup
16
16
  def initialize(account)
17
17
  @account = account
18
18
  reset
19
- create_account_folder
20
19
  end
21
20
 
22
- # TODO: Make this private once the 'folders' command
23
- # has been removed.
24
21
  def folder_names
25
22
  @folder_names ||=
26
23
  begin
@@ -53,6 +50,7 @@ module Imap::Backup
53
50
  end
54
51
 
55
52
  def status
53
+ ensure_account_folder
56
54
  backup_folders.map do |folder|
57
55
  s = Serializer::Mbox.new(account.local_path, folder.name)
58
56
  {name: folder.name, local: s.uids, remote: folder.uids}
@@ -63,6 +61,7 @@ module Imap::Backup
63
61
  Imap::Backup::Logger.logger.debug "Running backup of account: #{account.username}"
64
62
  # start the connection so we get logging messages in the right order
65
63
  client
64
+ ensure_account_folder
66
65
  each_folder do |folder, serializer|
67
66
  next if !folder.exist?
68
67
 
@@ -82,6 +81,7 @@ module Imap::Backup
82
81
  def local_folders
83
82
  return enum_for(:local_folders) if !block_given?
84
83
 
84
+ ensure_account_folder
85
85
  glob = File.join(account.local_path, "**", "*.imap")
86
86
  base = Pathname.new(account.local_path)
87
87
  Pathname.glob(glob) do |path|
@@ -176,7 +176,7 @@ module Imap::Backup
176
176
  end
177
177
  end
178
178
 
179
- def create_account_folder
179
+ def ensure_account_folder
180
180
  Utils.make_folder(
181
181
  File.dirname(account.local_path),
182
182
  File.basename(account.local_path),
@@ -111,6 +111,14 @@ module Imap::Backup
111
111
  extract_uid(response)
112
112
  end
113
113
 
114
+ def clear
115
+ existing = uids
116
+ # Use read-write access, via `select`
117
+ client.select(utf7_encoded_name)
118
+ client.uid_store(existing, "+FLAGS", [:Deleted])
119
+ client.expunge
120
+ end
121
+
114
122
  private
115
123
 
116
124
  def examine
@@ -13,7 +13,7 @@ module Imap::Backup
13
13
  no_commands do
14
14
  def run
15
15
  each_connection(account_names) do |connection|
16
- puts connection.username
16
+ puts connection.account.username
17
17
  # TODO: Make folder_names private once this command
18
18
  # has been removed.
19
19
  folders = connection.folder_names
@@ -3,7 +3,10 @@ require "imap/backup/cli/accounts"
3
3
 
4
4
  module Imap::Backup::CLI::Helpers
5
5
  def symbolized(options)
6
- options.each.with_object({}) { |(k, v), acc| acc[k.intern] = v }
6
+ options.each.with_object({}) do |(k, v), acc|
7
+ key = k.gsub("-", "_").intern
8
+ acc[key] = v
9
+ end
7
10
  end
8
11
 
9
12
  def account(email)
@@ -11,7 +11,7 @@ module Imap::Backup
11
11
  accounts.each { |a| Kernel.puts a.username }
12
12
  end
13
13
 
14
- desc "folders EMAIL", "List account folders"
14
+ desc "folders EMAIL", "List backed up folders"
15
15
  def folders(email)
16
16
  connection = connection(email)
17
17
 
@@ -0,0 +1,114 @@
1
+ require "imap/backup/migrator"
2
+
3
+ module Imap::Backup
4
+ class CLI::Migrate < Thor
5
+ include Thor::Actions
6
+
7
+ attr_reader :destination_email
8
+ attr_reader :destination_prefix
9
+ attr_reader :reset
10
+ attr_reader :source_email
11
+ attr_reader :source_prefix
12
+
13
+ def initialize(
14
+ source_email,
15
+ destination_email,
16
+ destination_prefix: "",
17
+ reset: false,
18
+ source_prefix: ""
19
+ )
20
+ super([])
21
+ @destination_email = destination_email
22
+ @destination_prefix = destination_prefix
23
+ @reset = reset
24
+ @source_email = source_email
25
+ @source_prefix = source_prefix
26
+ end
27
+
28
+ no_commands do
29
+ def run
30
+ check_accounts!
31
+ folders.each do |serializer, folder|
32
+ Migrator.new(serializer, folder, reset: reset).run
33
+ end
34
+ end
35
+
36
+ def check_accounts!
37
+ if destination_email == source_email
38
+ raise "Source and destination accounts cannot be the same!"
39
+ end
40
+
41
+ if !destination_account
42
+ raise "Account #{destination_email} does not exist"
43
+ end
44
+
45
+ if !source_account
46
+ raise "Account #{source_email} does not exist"
47
+ end
48
+ end
49
+
50
+ def config
51
+ Configuration.new
52
+ end
53
+
54
+ def destination_account
55
+ config.accounts.find { |a| a.username == destination_email }
56
+ end
57
+
58
+ def folders
59
+ return enum_for(:folders) if !block_given?
60
+
61
+ glob = File.join(source_local_path, "**", "*.imap")
62
+ Pathname.glob(glob) do |path|
63
+ name = source_folder_name(path)
64
+ serializer = Serializer::Mbox.new(source_local_path, name)
65
+ folder = folder_for(name)
66
+ yield serializer, folder
67
+ end
68
+ end
69
+
70
+ def folder_for(source_folder)
71
+ no_source_prefix =
72
+ if source_prefix != "" &&
73
+ source_folder.start_with?(source_prefix)
74
+ source_folder.delete_prefix(source_prefix)
75
+ else
76
+ source_folder.to_s
77
+ end
78
+
79
+ with_destination_prefix =
80
+ if destination_prefix &&
81
+ destination_prefix != ""
82
+ destination_prefix + no_source_prefix
83
+ else
84
+ no_source_prefix
85
+ end
86
+
87
+ Account::Folder.new(
88
+ destination_account.connection,
89
+ with_destination_prefix
90
+ )
91
+ end
92
+
93
+ def source_local_path
94
+ source_account.local_path
95
+ end
96
+
97
+ def source_account
98
+ config.accounts.find { |a| a.username == source_email }
99
+ end
100
+
101
+ def source_folder_name(imap_pathname)
102
+ base = Pathname.new(source_local_path)
103
+ imap_name = imap_pathname.relative_path_from(base).to_s
104
+ dir = File.dirname(imap_name)
105
+ stripped = File.basename(imap_name, ".imap")
106
+ if dir == "."
107
+ stripped
108
+ else
109
+ File.join(dir, stripped)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,15 @@
1
+ module Imap::Backup
2
+ class CLI::Remote < Thor
3
+ include Thor::Actions
4
+ include CLI::Helpers
5
+
6
+ desc "folders EMAIL", "List account folders"
7
+ def folders(email)
8
+ connection = connection(email)
9
+
10
+ connection.folder_names.each do |name|
11
+ Kernel.puts %("#{name}")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,16 +3,32 @@ module Imap::Backup
3
3
  include Thor::Actions
4
4
  include CLI::Helpers
5
5
 
6
+ attr_reader :email
6
7
  attr_reader :account_names
7
8
 
8
- def initialize(options)
9
- super([])
10
- @account_names = (options[:accounts] || "").split(",")
9
+ def initialize(email = nil, options)
10
+ @email = email
11
+ @account_names =
12
+ if options.key?(:accounts)
13
+ options[:accounts].split(",")
14
+ end
11
15
  end
12
16
 
13
17
  no_commands do
14
18
  def run
15
- each_connection(account_names) { |connection| connection.restore }
19
+ case
20
+ when email && !account_names
21
+ connection = connection(email)
22
+ connection.restore
23
+ when !email && !account_names
24
+ Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
25
+ each_connection([]) { |connection| connection.restore }
26
+ when email && account_names.any?
27
+ raise "Pass either an email or the --accounts option, not both"
28
+ when account_names.any?
29
+ Logger.logger.info "Calling restore with the --account option is deprected, please pass a single EMAIL argument"
30
+ each_connection(account_names) { |connection| connection.restore }
31
+ end
16
32
  end
17
33
  end
18
34
  end
@@ -9,6 +9,7 @@ module Imap::Backup
9
9
  autoload :Backup, "imap/backup/cli/backup"
10
10
  autoload :Folders, "imap/backup/cli/folders"
11
11
  autoload :Local, "imap/backup/cli/local"
12
+ autoload :Migrate, "imap/backup/cli/migrate"
12
13
  autoload :Remote, "imap/backup/cli/remote"
13
14
  autoload :Restore, "imap/backup/cli/restore"
14
15
  autoload :Setup, "imap/backup/cli/setup"
@@ -27,7 +28,7 @@ module Imap::Backup
27
28
  method_option(
28
29
  "accounts",
29
30
  type: :string,
30
- banner: "a comma-separated list of accounts (defaults to all configured accounts)",
31
+ desc: "a comma-separated list of accounts (defaults to all configured accounts)",
31
32
  aliases: ["-a"]
32
33
  )
33
34
  end
@@ -57,15 +58,62 @@ module Imap::Backup
57
58
  Folders.new(symbolized(options)).run
58
59
  end
59
60
 
60
- desc "restore [OPTIONS]", "This command is deprecated, use `imap-backup restore ACCOUNT`"
61
+ desc "local SUBCOMMAND [OPTIONS]", "View local info"
62
+ subcommand "local", Local
63
+
64
+ desc "migrate SOURCE_EMAIL DESTINATION_EMAIL [OPTIONS]",
65
+ "[Experimental] Uploads backed-up emails from account SOURCE_EMAIL to account DESTINATION_EMAIL"
61
66
  long_desc <<~DESC
62
- By default, restores all local emails to their respective servers.
63
- This command is deprecated.
64
- Instead, use `imap-backup restore ACCOUNT` to restore a single account.
67
+ All emails which have been backed up for the "source account" (SOURCE_EMAIL) are
68
+ uploaded to the "destination account" (DESTINATION_EMAIL).
69
+
70
+ When one or other account has namespaces (i.e. prefixes like "INBOX."),
71
+ use the `--source-prefix=` and/or `--destination-prefix=` options.
72
+
73
+ Usually, you should migrate to an account with empty folders.
74
+
75
+ Before migrating each folder, `imap-backup` checks if the destination
76
+ folder is empty.
77
+
78
+ If it finds a non-empty destination folder, it halts with an error.
79
+
80
+ If you are sure that these destination emails can be deleted,
81
+ use the `--reset` option. In this case, all existing emails are
82
+ deleted before uploading the migrated emails.
83
+ DESC
84
+ method_option(
85
+ "destination-prefix",
86
+ type: :string,
87
+ desc: "the prefix (namespace) to add to destination folder names",
88
+ aliases: ["-d"]
89
+ )
90
+ method_option(
91
+ "reset",
92
+ type: :boolean,
93
+ desc: "DANGER! This option deletes all messages from destination folders before uploading",
94
+ aliases: ["-r"]
95
+ )
96
+ method_option(
97
+ "source-prefix",
98
+ type: :string,
99
+ desc: "the prefix (namespace) to strip from source folder names",
100
+ aliases: ["-s"]
101
+ )
102
+ def migrate(source_email, destination_email)
103
+ Migrate.new(source_email, destination_email, symbolized(options)).run
104
+ end
105
+
106
+ desc "remote SUBCOMMAND [OPTIONS]", "View info about online accounts"
107
+ subcommand "remote", Remote
108
+
109
+ desc "restore EMAIL", "Restores a single account"
110
+ long_desc <<~DESC
111
+ Restores all backed-up emails for the supplied account to
112
+ their original server.
65
113
  DESC
66
114
  accounts_option
67
- def restore
68
- Restore.new(symbolized(options)).run
115
+ def restore(email = nil)
116
+ Restore.new(email, symbolized(options)).run
69
117
  end
70
118
 
71
119
  desc "setup", "Configure imap-backup"
@@ -86,9 +134,6 @@ module Imap::Backup
86
134
  Status.new(symbolized(options)).run
87
135
  end
88
136
 
89
- desc "local SUBCOMMAND [OPTIONS]", "View local info"
90
- subcommand "local", Local
91
-
92
137
  desc "utils SUBCOMMAND [OPTIONS]", "Various utilities"
93
138
  subcommand "utils", Utils
94
139
  end
@@ -7,8 +7,8 @@ module Imap::Backup
7
7
  class Client::Default
8
8
  extend Forwardable
9
9
  def_delegators :imap, *%i(
10
- append authenticate create disconnect examine
11
- login responses uid_fetch uid_search
10
+ append authenticate create disconnect examine expunge
11
+ login responses select uid_fetch uid_search uid_store
12
12
  )
13
13
 
14
14
  attr_reader :args
@@ -13,31 +13,45 @@ module Imap::Backup
13
13
  def run
14
14
  uids = folder.uids - serializer.uids
15
15
  count = uids.count
16
- Imap::Backup::Logger.logger.debug "[#{folder.name}] #{count} new messages"
16
+ debug "#{count} new messages"
17
17
  uids.each_slice(block_size).with_index do |block, i|
18
- offset = i * block_size + 1
19
18
  uids_and_bodies = folder.fetch_multi(block)
20
19
  if uids_and_bodies.nil?
21
20
  if block_size > 1
22
- Imap::Backup::Logger.logger.debug("[#{folder.name}] Multi fetch failed for UIDs #{block.join(", ")}, switching to single fetches")
21
+ debug("Multi fetch failed for UIDs #{block.join(", ")}, switching to single fetches")
23
22
  @block_size = 1
24
23
  redo
25
24
  else
26
- Imap::Backup::Logger.logger.debug("[#{folder.name}] Fetch failed for UID #{block[0]} - skipping")
25
+ debug("Fetch failed for UID #{block[0]} - skipping")
27
26
  next
28
27
  end
29
28
  end
30
29
 
30
+ offset = i * block_size + 1
31
31
  uids_and_bodies.each.with_index do |uid_and_body, j|
32
32
  uid = uid_and_body[:uid]
33
33
  body = uid_and_body[:body]
34
- Imap::Backup::Logger.logger.debug(
35
- "[#{folder.name}] uid: #{uid} (#{offset +j}/#{count}) - " \
36
- "#{body.size} bytes"
37
- )
38
- serializer.save(uid, body)
34
+ case
35
+ when !body
36
+ info("Fetch returned empty body - skipping")
37
+ when !uid
38
+ info("Fetch returned empty UID - skipping")
39
+ else
40
+ debug("uid: #{uid} (#{offset + j}/#{count}) - #{body.size} bytes")
41
+ serializer.save(uid, body)
42
+ end
39
43
  end
40
44
  end
41
45
  end
46
+
47
+ private
48
+
49
+ def info(message)
50
+ Imap::Backup::Logger.logger.info("[#{folder.name}] #{message}")
51
+ end
52
+
53
+ def debug(message)
54
+ Imap::Backup::Logger.logger.debug("[#{folder.name}] #{message}")
55
+ end
42
56
  end
43
57
  end
@@ -0,0 +1,51 @@
1
+ module Imap::Backup
2
+ class Migrator
3
+ attr_reader :folder
4
+ attr_reader :reset
5
+ attr_reader :serializer
6
+
7
+ def initialize(serializer, folder, reset: false)
8
+ @folder = folder
9
+ @reset = reset
10
+ @serializer = serializer
11
+ end
12
+
13
+ def run
14
+ count = serializer.uids.count
15
+ folder.create
16
+ ensure_destination_empty!
17
+
18
+ Imap::Backup::Logger.logger.debug "[#{folder.name}] #{count} to migrate"
19
+ serializer.each_message(serializer.uids).with_index do |(uid, message), i|
20
+ next if message.nil?
21
+
22
+ log_prefix = "[#{folder.name}] uid: #{uid} (#{i + 1}/#{count}) -"
23
+ Imap::Backup::Logger.logger.debug(
24
+ "#{log_prefix} #{message.supplied_body.size} bytes"
25
+ )
26
+
27
+ folder.append(message)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def ensure_destination_empty!
34
+ if reset
35
+ folder.clear
36
+ else
37
+ fail_if_destination_not_empty!
38
+ end
39
+ end
40
+
41
+ def fail_if_destination_not_empty!
42
+ return if folder.uids.empty?
43
+
44
+ raise <<~ERROR
45
+ The destination folder '#{folder.name}' is not empty.
46
+ Pass the --reset flag if you want to clear existing emails from destination
47
+ folders before uploading.
48
+ ERROR
49
+ end
50
+ end
51
+ end
@@ -1,16 +1,19 @@
1
+ require "forwardable"
2
+
1
3
  module Imap::Backup
2
4
  class Sanitizer
5
+ extend Forwardable
6
+
3
7
  attr_reader :output
4
8
 
9
+ delegate puts: :output
10
+ delegate write: :output
11
+
5
12
  def initialize(output)
6
13
  @output = output
7
14
  @current = ""
8
15
  end
9
16
 
10
- def write(*args)
11
- output.write(*args)
12
- end
13
-
14
17
  def print(*args)
15
18
  @current << args.join
16
19
  loop do
@@ -1,8 +1,8 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 4
5
- MINOR = 2
4
+ MAJOR = 5
5
+ MINOR = 1
6
6
  REVISION = 0
7
7
  PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
@@ -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,11 +51,10 @@ 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
@@ -78,6 +77,7 @@ RSpec.describe "backup", type: :feature, docker: true do
78
77
  context "when a renamed local backup exists" do
79
78
  let!(:pre) do
80
79
  super()
80
+ create_directory local_backup_path
81
81
  File.write(imap_path(renamed_folder), "existing imap")
82
82
  File.write(mbox_path(renamed_folder), "existing mbox")
83
83
  end
@@ -91,6 +91,7 @@ RSpec.describe "backup", type: :feature, docker: true do
91
91
 
92
92
  context "when an unversioned .imap file is found" do
93
93
  let!(:pre) do
94
+ create_directory local_backup_path
94
95
  File.open(imap_path(folder), "w") { |f| f.write "old format imap" }
95
96
  File.open(mbox_path(folder), "w") { |f| f.write "old format emails" }
96
97
  end
@@ -0,0 +1,15 @@
1
+ require "features/helper"
2
+
3
+ RSpec.describe "Running commands", type: :aruba do
4
+ before do
5
+ create_config(accounts: [])
6
+ run_command("imap-backup local accounts")
7
+ last_command_started.stop
8
+ end
9
+
10
+ context "when no accounts are configured" do
11
+ it "succeeds" do
12
+ expect(last_command_started).to have_exit_status(0)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require "features/helper"
2
+
3
+ RSpec.describe "Running commands", type: :aruba do
4
+ before do
5
+ run_command("imap-backup")
6
+ last_command_started.stop
7
+ end
8
+
9
+ context "when the configuration file is missing" do
10
+ it "fails" do
11
+ expect(last_command_started).not_to have_exit_status(0)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ require "features/helper"
2
+ require "imap/backup/cli/folders"
3
+
4
+ RSpec.describe "folders", type: :feature, docker: true do
5
+ include_context "imap-backup connection"
6
+
7
+ let(:options) do
8
+ {accounts: "address@example.org"}
9
+ end
10
+ let(:folder) { "my-stuff" }
11
+ let(:output) { StringIO.new }
12
+
13
+ before do
14
+ allow(Imap::Backup::CLI::Accounts).to receive(:new) { [account] }
15
+ server_create_folder folder
16
+ end
17
+
18
+ around do |example|
19
+ stdout = $stdout
20
+ $stdout = output
21
+ example.run
22
+ $stdout = stdout
23
+ end
24
+
25
+ after do
26
+ FileUtils.rm_rf local_backup_path
27
+ server_delete_folder folder
28
+ connection.disconnect
29
+ end
30
+
31
+ it "lists account folders" do
32
+ Imap::Backup::CLI::Folders.new(options).run
33
+
34
+ expect(output.string).to match(/^\tmy-stuff\n/)
35
+ end
36
+ end