imap-backup 4.0.5 → 4.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6eebcf60acbb2e97a007e5da03c63fb7c32e24413397f28d3ec9f94154b8def5
4
- data.tar.gz: 5f81961b55811c8c9fadabf2def11e1e00ab8572fd844f44b8fda371aa190bbb
3
+ metadata.gz: 7b543b8f8b913b95b68c9ad9532143c375d90b468b0977982faa3522c0d3932b
4
+ data.tar.gz: '0939b42c8dad41d14212a18444d94c209ce025646683faded89b9808e6181416'
5
5
  SHA512:
6
- metadata.gz: 6ebb0d93413c0911487235584837f76f6409d66cc06a64ad9e7db7ec0a4ccca2694b9be363e02922bbf1c981d944a9772e6705190ad92b461252d7b57bf1e4b0
7
- data.tar.gz: 6f29640122bf11c098050476e60caa073ebb868a0859624d6e7b74df435e85a1d6ebbcf964840058c6978a42d16034d86341741caead66d99722554ffc2ca4a3
6
+ metadata.gz: 52f9058554faa9a3d31bef3cb7254408032cdddee98293ea5f7c8ef108d3187b564b7fd71a18bdb1b31829b46ed22bdd51626704f82e98d88076bd4cec887d44
7
+ data.tar.gz: 186036e2f11ed8e0ae578ab44d73b2b4c1f89bf9c65eedc66a143b4d1b861adacba1d954190d566c399cc79184ba3645ebb53a3341ee82a03ad9b5d5d7fa777d
@@ -4,25 +4,17 @@ require "imap/backup/client/default"
4
4
  require "retry_on_error"
5
5
 
6
6
  module Imap::Backup
7
- module Account; end
7
+ class Account; end
8
8
 
9
9
  class Account::Connection
10
10
  include RetryOnError
11
11
 
12
12
  LOGIN_RETRY_CLASSES = [EOFError, Errno::ECONNRESET, SocketError].freeze
13
13
 
14
- attr_reader :connection_options
15
- attr_reader :local_path
16
- attr_reader :password
17
- attr_reader :username
18
-
19
- def initialize(options)
20
- @username = options[:username]
21
- @password = options[:password]
22
- @local_path = options[:local_path]
23
- @config_folders = options[:folders]
24
- @server = options[:server]
25
- @connection_options = options[:connection_options] || {}
14
+ attr_reader :account
15
+
16
+ def initialize(account)
17
+ @account = account
26
18
  @folders = nil
27
19
  create_account_folder
28
20
  end
@@ -33,7 +25,7 @@ module Imap::Backup
33
25
  folders = client.list
34
26
 
35
27
  if folders.empty?
36
- message = "Unable to get folder list for account #{username}"
28
+ message = "Unable to get folder list for account #{account.username}"
37
29
  Imap::Backup.logger.info message
38
30
  raise message
39
31
  end
@@ -45,13 +37,13 @@ module Imap::Backup
45
37
  def status
46
38
  backup_folders.map do |backup_folder|
47
39
  f = Account::Folder.new(self, backup_folder[:name])
48
- s = Serializer::Mbox.new(local_path, backup_folder[:name])
40
+ s = Serializer::Mbox.new(account.local_path, backup_folder[:name])
49
41
  {name: backup_folder[:name], local: s.uids, remote: f.uids}
50
42
  end
51
43
  end
52
44
 
53
45
  def run_backup
54
- Imap::Backup.logger.debug "Running backup of account: #{username}"
46
+ Imap::Backup.logger.debug "Running backup of account: #{account.username}"
55
47
  # start the connection so we get logging messages in the right order
56
48
  client
57
49
  each_folder do |folder, serializer|
@@ -71,11 +63,11 @@ module Imap::Backup
71
63
  def local_folders
72
64
  return enum_for(:local_folders) if !block_given?
73
65
 
74
- glob = File.join(local_path, "**", "*.imap")
75
- base = Pathname.new(local_path)
66
+ glob = File.join(account.local_path, "**", "*.imap")
67
+ base = Pathname.new(account.local_path)
76
68
  Pathname.glob(glob) do |path|
77
69
  name = path.relative_path_from(base).to_s[0..-6]
78
- serializer = Serializer::Mbox.new(local_path, name)
70
+ serializer = Serializer::Mbox.new(account.local_path, name)
79
71
  folder = Account::Folder.new(self, name)
80
72
  yield serializer, folder
81
73
  end
@@ -109,15 +101,15 @@ module Imap::Backup
109
101
  else
110
102
  Client::Default.new(server, options)
111
103
  end
112
- Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
113
- client.login(username, password)
104
+ Imap::Backup.logger.debug "Logging in: #{account.username}/#{masked_password}"
105
+ client.login(account.username, account.password)
114
106
  Imap::Backup.logger.debug "Login complete"
115
107
  client
116
108
  end
117
109
  end
118
110
 
119
111
  def server
120
- @server ||= provider.host
112
+ @server ||= account.server || provider.host
121
113
  end
122
114
 
123
115
  private
@@ -125,7 +117,7 @@ module Imap::Backup
125
117
  def each_folder
126
118
  backup_folders.each do |backup_folder|
127
119
  folder = Account::Folder.new(self, backup_folder[:name])
128
- serializer = Serializer::Mbox.new(local_path, backup_folder[:name])
120
+ serializer = Serializer::Mbox.new(account.local_path, backup_folder[:name])
129
121
  yield folder, serializer
130
122
  end
131
123
  end
@@ -142,7 +134,7 @@ module Imap::Backup
142
134
  Imap::Backup.logger.debug(
143
135
  "Backup '#{old_name}' renamed and restored to '#{new_name}'"
144
136
  )
145
- new_serializer = Serializer::Mbox.new(local_path, new_name)
137
+ new_serializer = Serializer::Mbox.new(account.local_path, new_name)
146
138
  new_folder = Account::Folder.new(self, new_name)
147
139
  new_folder.create
148
140
  new_serializer.force_uid_validity(new_folder.uid_validity)
@@ -159,21 +151,21 @@ module Imap::Backup
159
151
 
160
152
  def create_account_folder
161
153
  Utils.make_folder(
162
- File.dirname(local_path),
163
- File.basename(local_path),
154
+ File.dirname(account.local_path),
155
+ File.basename(account.local_path),
164
156
  Serializer::DIRECTORY_PERMISSIONS
165
157
  )
166
158
  end
167
159
 
168
160
  def masked_password
169
- password.gsub(/./, "x")
161
+ account.password.gsub(/./, "x")
170
162
  end
171
163
 
172
164
  def backup_folders
173
165
  @backup_folders ||=
174
166
  begin
175
- if @config_folders&.any?
176
- @config_folders
167
+ if account.folders&.any?
168
+ account.folders
177
169
  else
178
170
  folders.map { |name| {name: name} }
179
171
  end
@@ -181,11 +173,11 @@ module Imap::Backup
181
173
  end
182
174
 
183
175
  def provider
184
- @provider ||= Email::Provider.for_address(username)
176
+ @provider ||= Email::Provider.for_address(account.username)
185
177
  end
186
178
 
187
179
  def provider_options
188
- provider.options.merge(connection_options)
180
+ provider.options.merge(account.connection_options || {})
189
181
  end
190
182
  end
191
183
  end
@@ -3,7 +3,7 @@ require "forwardable"
3
3
  require "retry_on_error"
4
4
 
5
5
  module Imap::Backup
6
- module Account; end
6
+ class Account; end
7
7
 
8
8
  class FolderNotFound < StandardError; end
9
9
 
@@ -83,14 +83,9 @@ module Imap::Backup
83
83
  def update(field, value)
84
84
  if changes[field]
85
85
  change = changes[field]
86
- if change[:from] == value
87
- changes.delete(field)
88
- else
89
- set_field!(field, value)
90
- end
91
- else
92
- set_field!(field, value)
86
+ changes.delete(field) if change[:from] == value
93
87
  end
88
+ set_field!(field, value)
94
89
  end
95
90
 
96
91
  def set_field!(field, value)
@@ -7,7 +7,7 @@ module Imap::Backup::CLI::Helpers
7
7
 
8
8
  def account(email)
9
9
  connections = Imap::Backup::Configuration::List.new
10
- account = connections.accounts.find { |a| a[:username] == email }
10
+ account = connections.accounts.find { |a| a.username == email }
11
11
  raise "#{email} is not a configured account" if !account
12
12
 
13
13
  account
@@ -6,7 +6,7 @@ module Imap::Backup
6
6
  desc "accounts", "List locally backed-up accounts"
7
7
  def accounts
8
8
  connections = Imap::Backup::Configuration::List.new
9
- connections.accounts.each { |a| Kernel.puts a[:username] }
9
+ connections.accounts.each { |a| Kernel.puts a.username }
10
10
  end
11
11
 
12
12
  desc "folders EMAIL", "List account folders"
@@ -22,9 +22,10 @@ module Imap::Backup
22
22
  header menu
23
23
  modify_email menu
24
24
  modify_password menu
25
- modify_server menu
26
25
  modify_backup_path menu
27
26
  choose_folders menu
27
+ modify_server menu
28
+ modify_connection_options menu
28
29
  test_connection menu
29
30
  delete_account menu
30
31
  menu.choice("return to main menu") { throw :done }
@@ -33,13 +34,20 @@ module Imap::Backup
33
34
  end
34
35
 
35
36
  def header(menu)
36
- menu.header = <<-HEADER.gsub(/^\s{8}/m, "")
37
+ connection_options =
38
+ if account.connection_options
39
+ escaped =
40
+ JSON.generate(account.connection_options).
41
+ gsub('"', '\"')
42
+ "\n connection options: #{escaped}"
43
+ end
44
+ menu.header = <<~HEADER
37
45
  Account:
38
- email: #{account[:username]}
39
- server: #{account[:server]}
40
- path: #{account[:local_path]}
41
- folders: #{folders.map { |f| f[:name] }.join(', ')}
46
+ email: #{account.username}
42
47
  password: #{masked_password}
48
+ path: #{account.local_path}
49
+ folders: #{folders.map { |f| f[:name] }.join(', ')}
50
+ server: #{account.server}#{connection_options}
43
51
  HEADER
44
52
  end
45
53
 
@@ -48,20 +56,20 @@ module Imap::Backup
48
56
  username = Configuration::Asker.email(username)
49
57
  Kernel.puts "username: #{username}"
50
58
  other_accounts = store.accounts.reject { |a| a == account }
51
- others = other_accounts.map { |a| a[:username] }
59
+ others = other_accounts.map { |a| a.username }
52
60
  Kernel.puts "others: #{others.inspect}"
53
61
  if others.include?(username)
54
62
  Kernel.puts(
55
63
  "There is already an account set up with that email address"
56
64
  )
57
65
  else
58
- account[:username] = username
66
+ account.username = username
59
67
  # rubocop:disable Style/IfUnlessModifier
60
- if account[:server].nil? || (account[:server] == "")
61
- account[:server] = default_server(username)
68
+ default = default_server(username)
69
+ if default && (account.server.nil? || (account.server == ""))
70
+ account.server = default
62
71
  end
63
72
  # rubocop:enable Style/IfUnlessModifier
64
- account[:modified] = true
65
73
  end
66
74
  end
67
75
  end
@@ -70,30 +78,31 @@ module Imap::Backup
70
78
  menu.choice("modify password") do
71
79
  password = Configuration::Asker.password
72
80
 
73
- if !password.nil?
74
- account[:password] = password
75
- account[:modified] = true
76
- end
81
+ account.password = password if !password.nil?
77
82
  end
78
83
  end
79
84
 
80
85
  def modify_server(menu)
81
86
  menu.choice("modify server") do
82
87
  server = highline.ask("server: ")
83
- if !server.nil?
84
- account[:server] = server
85
- account[:modified] = true
86
- end
88
+ account.server = server if !server.nil?
89
+ end
90
+ end
91
+
92
+ def modify_connection_options(menu)
93
+ menu.choice("modify connection options") do
94
+ connection_options = highline.ask("connections options (as JSON): ")
95
+ account.connection_options = connection_options if !connection_options.nil?
87
96
  end
88
97
  end
89
98
 
90
99
  def path_modification_validator(path)
91
100
  same = store.accounts.find do |a|
92
- a[:username] != account[:username] && a[:local_path] == path
101
+ a.username != account.username && a.local_path == path
93
102
  end
94
103
  if same
95
104
  Kernel.puts "The path '#{path}' is used to backup " \
96
- "the account '#{same[:username]}'"
105
+ "the account '#{same.username}'"
97
106
  false
98
107
  else
99
108
  true
@@ -102,11 +111,10 @@ module Imap::Backup
102
111
 
103
112
  def modify_backup_path(menu)
104
113
  menu.choice("modify backup path") do
105
- existing = account[:local_path].clone
106
- account[:local_path] = Configuration::Asker.backup_path(
107
- account[:local_path], ->(path) { path_modification_validator(path) }
114
+ existing = account.local_path.clone
115
+ account.local_path = Configuration::Asker.backup_path(
116
+ account.local_path, ->(path) { path_modification_validator(path) }
108
117
  )
109
- account[:modified] = true if existing != account[:local_path]
110
118
  end
111
119
  end
112
120
 
@@ -127,21 +135,21 @@ module Imap::Backup
127
135
  def delete_account(menu)
128
136
  menu.choice("delete") do
129
137
  if highline.agree("Are you sure? (y/n) ")
130
- account[:delete] = true
138
+ account.mark_for_deletion!
131
139
  throw :done
132
140
  end
133
141
  end
134
142
  end
135
143
 
136
144
  def folders
137
- account[:folders] || []
145
+ account.folders || []
138
146
  end
139
147
 
140
148
  def masked_password
141
- if (account[:password] == "") || account[:password].nil?
149
+ if (account.password == "") || account.password.nil?
142
150
  "(unset)"
143
151
  else
144
- account[:password].gsub(/./, "x")
152
+ account.password.gsub(/./, "x")
145
153
  end
146
154
  end
147
155
 
@@ -53,7 +53,7 @@ module Imap::Backup
53
53
  end
54
54
 
55
55
  def selected?(folder_name)
56
- config_folders = account[:folders]
56
+ config_folders = account.folders
57
57
  return false if config_folders.nil?
58
58
 
59
59
  config_folders.find { |f| f[:name] == folder_name }
@@ -62,7 +62,7 @@ module Imap::Backup
62
62
  def remove_missing
63
63
  removed = []
64
64
  config_folders = []
65
- account[:folders].each do |f|
65
+ account.folders.each do |f|
66
66
  found = imap_folders.find { |folder| folder == f[:name] }
67
67
  if found
68
68
  config_folders << f
@@ -73,8 +73,7 @@ module Imap::Backup
73
73
 
74
74
  return if removed.empty?
75
75
 
76
- account[:folders] = config_folders
77
- account[:modified] = true
76
+ account.folders = config_folders
78
77
 
79
78
  Kernel.puts <<~MESSAGE
80
79
  The following folders have been removed: #{removed.join(', ')}
@@ -85,12 +84,11 @@ module Imap::Backup
85
84
 
86
85
  def toggle_selection(folder_name)
87
86
  if selected?(folder_name)
88
- changed = account[:folders].reject! { |f| f[:name] == folder_name }
89
- account[:modified] = true if changed
87
+ new_list = account.folders.select { |f| f[:name] != folder_name }
88
+ account.folders = new_list
90
89
  else
91
- account[:folders] ||= []
92
- account[:folders] << {name: folder_name}
93
- account[:modified] = true
90
+ existing = account.folders || []
91
+ account.folders = existing + [{name: folder_name}]
94
92
  end
95
93
  end
96
94
 
@@ -1,5 +1,7 @@
1
1
  require "highline"
2
2
 
3
+ require "imap/backup/account"
4
+
3
5
  module Imap::Backup
4
6
  module Configuration; end
5
7
 
@@ -39,12 +41,12 @@ module Imap::Backup
39
41
 
40
42
  def account_items(menu)
41
43
  config.accounts.each do |account|
42
- next if account[:delete]
44
+ next if account.marked_for_deletion?
43
45
 
44
- item = account[:username].clone
45
- item << " *" if account[:modified]
46
+ item = account.username.clone
47
+ item << " *" if account.modified?
46
48
  menu.choice(item) do
47
- edit_account account[:username]
49
+ edit_account account.username
48
50
  end
49
51
  end
50
52
  end
@@ -70,19 +72,19 @@ module Imap::Backup
70
72
  end
71
73
 
72
74
  def default_account_config(username)
73
- {
75
+ ::Imap::Backup::Account.new(
74
76
  username: username,
75
77
  password: "",
76
78
  local_path: File.join(config.path, username.tr("@", "_")),
77
79
  folders: []
78
- }.tap do |c|
80
+ ).tap do |a|
79
81
  server = Email::Provider.for_address(username)
80
- c[:server] = server.host if server.host
82
+ a.server = server.host if server.host
81
83
  end
82
84
  end
83
85
 
84
86
  def edit_account(username)
85
- account = config.accounts.find { |a| a[:username] == username }
87
+ account = config.accounts.find { |a| a.username == username }
86
88
  if account.nil?
87
89
  account = default_account_config(username)
88
90
  config.accounts << account
@@ -1,11 +1,14 @@
1
1
  require "json"
2
2
  require "os"
3
3
 
4
+ require "imap/backup/account"
5
+
4
6
  module Imap::Backup
5
7
  module Configuration; end
6
8
 
7
9
  class Configuration::Store
8
10
  CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
11
+ VERSION = "2.0"
9
12
 
10
13
  attr_reader :pathname
11
14
 
@@ -19,6 +22,7 @@ module Imap::Backup
19
22
 
20
23
  def initialize(pathname = self.class.default_pathname)
21
24
  @pathname = pathname
25
+ @debug = nil
22
26
  end
23
27
 
24
28
  def path
@@ -26,53 +30,71 @@ module Imap::Backup
26
30
  end
27
31
 
28
32
  def save
33
+ ensure_loaded!
29
34
  FileUtils.mkdir(path) if !File.directory?(path)
30
35
  make_private(path) if !windows?
31
36
  remove_modified_flags
32
37
  remove_deleted_accounts
33
- File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(data)) }
38
+ save_data = {
39
+ version: VERSION,
40
+ accounts: accounts.map(&:to_h),
41
+ debug: debug?
42
+ }
43
+ File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
34
44
  FileUtils.chmod(0o600, pathname) if !windows?
35
45
  end
36
46
 
37
47
  def accounts
38
- data[:accounts]
48
+ @accounts ||= begin
49
+ ensure_loaded!
50
+ data[:accounts].map { |data| Account.new(data) }
51
+ end
39
52
  end
40
53
 
41
54
  def modified?
42
- accounts.any? { |a| a[:modified] || a[:delete] }
55
+ ensure_loaded!
56
+ accounts.any? { |a| a.modified? || a.marked_for_deletion? }
43
57
  end
44
58
 
45
59
  def debug?
46
- data[:debug]
60
+ ensure_loaded!
61
+ @debug
47
62
  end
48
63
 
49
64
  def debug=(value)
50
- data[:debug] = [true, false].include?(value) ? value : false
65
+ ensure_loaded!
66
+ @debug = [true, false].include?(value) ? value : false
51
67
  end
52
68
 
53
69
  private
54
70
 
71
+ def ensure_loaded!
72
+ return true if @data
73
+
74
+ data
75
+ @debug = data.key?(:debug) ? data[:debug] == true : false
76
+ true
77
+ end
78
+
55
79
  def data
56
80
  @data ||=
57
81
  begin
58
82
  if File.exist?(pathname)
59
83
  Utils.check_permissions(pathname, 0o600) if !windows?
60
84
  contents = File.read(pathname)
61
- data = JSON.parse(contents, symbolize_names: true)
85
+ JSON.parse(contents, symbolize_names: true)
62
86
  else
63
- data = {accounts: []}
87
+ {accounts: []}
64
88
  end
65
- data[:debug] = data.key?(:debug) ? data[:debug] == true : false
66
- data
67
89
  end
68
90
  end
69
91
 
70
92
  def remove_modified_flags
71
- accounts.each { |a| a.delete(:modified) }
93
+ accounts.each { |a| a.clear_changes! }
72
94
  end
73
95
 
74
96
  def remove_deleted_accounts
75
- accounts.reject! { |a| a[:delete] }
97
+ accounts.reject! { |a| a.marked_for_deletion? }
76
98
  end
77
99
 
78
100
  def make_private(path)
@@ -3,7 +3,7 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  MAJOR = 4
5
5
  MINOR = 0
6
- REVISION = 5
6
+ REVISION = 6
7
7
  PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
@@ -2,11 +2,13 @@ shared_context "imap-backup connection" do
2
2
  let(:local_backup_path) { Dir.mktmpdir(nil, "tmp") }
3
3
  let(:default_connection) { fixture("connection") }
4
4
  let(:backup_folders) { nil }
5
- let(:connection_options) do
6
- default_connection.merge(
7
- local_path: local_backup_path,
8
- folders: backup_folders
5
+ let(:account) do
6
+ Imap::Backup::Account.new(
7
+ default_connection.merge(
8
+ local_path: local_backup_path,
9
+ folders: backup_folders
10
+ )
9
11
  )
10
12
  end
11
- let(:connection) { Imap::Backup::Account::Connection.new(connection_options) }
13
+ let(:connection) { Imap::Backup::Account::Connection.new(account) }
12
14
  end
@@ -1,4 +1,4 @@
1
- describe Email::Provider::Default do
1
+ describe Email::Provider::Base do
2
2
  describe "#options" do
3
3
  it "returns options" do
4
4
  expect(subject.options).to be_a(Hash)
@@ -17,10 +17,10 @@ describe Email::Provider do
17
17
  end
18
18
 
19
19
  context "with unknown providers" do
20
- it "returns a default provider" do
20
+ it "returns the Unknown provider" do
21
21
  result = described_class.for_address("foo@unknown.com")
22
22
 
23
- expect(result).to be_a(Email::Provider::Default)
23
+ expect(result).to be_a(Email::Provider::Unknown)
24
24
  end
25
25
  end
26
26
  end
@@ -12,7 +12,7 @@ describe Imap::Backup::Account::Connection do
12
12
  SERVER = "imap.example.com".freeze
13
13
  USERNAME = "username@example.com".freeze
14
14
 
15
- subject { described_class.new(options) }
15
+ subject { described_class.new(account) }
16
16
 
17
17
  let(:client) do
18
18
  instance_double(
@@ -20,14 +20,16 @@ describe Imap::Backup::Account::Connection do
20
20
  )
21
21
  end
22
22
  let(:imap_folders) { [] }
23
- let(:options) do
24
- {
23
+ let(:account) do
24
+ instance_double(
25
+ Imap::Backup::Account,
25
26
  username: USERNAME,
26
27
  password: PASSWORD,
27
28
  local_path: LOCAL_PATH,
28
29
  folders: config_folders,
29
- server: server
30
- }
30
+ server: server,
31
+ connection_options: nil
32
+ )
31
33
  end
32
34
  let(:config_folders) { [FOLDER_CONFIG] }
33
35
  let(:root_info) do
@@ -59,21 +61,10 @@ describe Imap::Backup::Account::Connection do
59
61
  end
60
62
 
61
63
  describe "#initialize" do
62
- [
63
- [:username, USERNAME],
64
- [:password, PASSWORD],
65
- [:local_path, LOCAL_PATH],
66
- [:server, SERVER]
67
- ].each do |attr, expected|
68
- it "expects #{attr}" do
69
- expect(subject.public_send(attr)).to eq(expected)
70
- end
71
- end
72
-
73
64
  it "creates the path" do
74
65
  expect(Imap::Backup::Utils).to receive(:make_folder)
75
66
 
76
- subject.username
67
+ subject
77
68
  end
78
69
  end
79
70
 
@@ -1,8 +1,15 @@
1
1
  describe Imap::Backup::CLI::Local do
2
2
  let(:list) do
3
- instance_double(Imap::Backup::Configuration::List, accounts: accounts)
3
+ instance_double(Imap::Backup::Configuration::List, accounts: [account])
4
+ end
5
+ let(:account) do
6
+ instance_double(
7
+ Imap::Backup::Account,
8
+ username: email,
9
+ marked_for_deletion?: false,
10
+ modified?: false
11
+ )
4
12
  end
5
- let(:accounts) { [{username: email}] }
6
13
  let(:connection) do
7
14
  instance_double(
8
15
  Imap::Backup::Account::Connection,
@@ -1,50 +1,52 @@
1
- describe Imap::Backup::CLI::Utils do
2
- let(:list) do
3
- instance_double(Imap::Backup::Configuration::List, accounts: accounts)
4
- end
5
- let(:accounts) { [{username: email}] }
6
- let(:connection) do
7
- instance_double(
8
- Imap::Backup::Account::Connection,
9
- local_folders: local_folders
10
- )
11
- end
12
- let(:local_folders) { [[serializer, folder]] }
13
- let(:folder) do
14
- instance_double(
15
- Imap::Backup::Account::Folder,
16
- exist?: true,
17
- name: "name",
18
- uid_validity: "uid_validity",
19
- uids: %w(123 456)
20
- )
21
- end
22
- let(:serializer) do
23
- instance_double(
24
- Imap::Backup::Serializer::Mbox,
25
- uids: %w(123 789),
26
- apply_uid_validity: nil,
27
- save: nil
28
- )
29
- end
30
- let(:email) { "foo@example.com" }
1
+ module Imap::Backup
2
+ describe CLI::Utils do
3
+ let(:list) do
4
+ instance_double(Configuration::List, accounts: [account])
5
+ end
6
+ let(:account) { instance_double(Account, username: email) }
7
+ let(:connection) do
8
+ instance_double(
9
+ Account::Connection,
10
+ local_folders: local_folders
11
+ )
12
+ end
13
+ let(:local_folders) { [[serializer, folder]] }
14
+ let(:folder) do
15
+ instance_double(
16
+ Account::Folder,
17
+ exist?: true,
18
+ name: "name",
19
+ uid_validity: "uid_validity",
20
+ uids: %w(123 456)
21
+ )
22
+ end
23
+ let(:serializer) do
24
+ instance_double(
25
+ Serializer::Mbox,
26
+ uids: %w(123 789),
27
+ apply_uid_validity: nil,
28
+ save: nil
29
+ )
30
+ end
31
+ let(:email) { "foo@example.com" }
31
32
 
32
- before do
33
- allow(Imap::Backup::Configuration::List).to receive(:new) { list }
34
- allow(Imap::Backup::Account::Connection).to receive(:new) { connection }
35
- end
33
+ before do
34
+ allow(Configuration::List).to receive(:new) { list }
35
+ allow(Account::Connection).to receive(:new) { connection }
36
+ end
36
37
 
37
- describe "ignore_history" do
38
- it "ensures the local UID validity matches the server" do
39
- subject.ignore_history(email)
38
+ describe "ignore_history" do
39
+ it "ensures the local UID validity matches the server" do
40
+ subject.ignore_history(email)
40
41
 
41
- expect(serializer).to have_received(:apply_uid_validity).with("uid_validity")
42
- end
42
+ expect(serializer).to have_received(:apply_uid_validity).with("uid_validity")
43
+ end
43
44
 
44
- it "fills the local folder with fake emails" do
45
- subject.ignore_history(email)
45
+ it "fills the local folder with fake emails" do
46
+ subject.ignore_history(email)
46
47
 
47
- expect(serializer).to have_received(:save).with("456", /From: fake@email.com/)
48
+ expect(serializer).to have_received(:save).with("456", /From: fake@email.com/)
49
+ end
48
50
  end
49
51
  end
50
52
  end
@@ -6,7 +6,32 @@ describe Imap::Backup::Configuration::Account do
6
6
 
7
7
  subject { described_class.new(store, account, highline) }
8
8
 
9
- let(:account) { ACCOUNT }
9
+ let(:account) do
10
+ instance_double(
11
+ Imap::Backup::Account,
12
+ username: existing_email,
13
+ password: existing_password,
14
+ server: current_server,
15
+ connection_options: nil,
16
+ local_path: "/backup/path",
17
+ folders: [{name: "my_folder"}]
18
+ )
19
+ end
20
+ let(:account1) do
21
+ instance_double(
22
+ Imap::Backup::Account,
23
+ username: other_email,
24
+ local_path: other_existing_path
25
+ )
26
+ end
27
+ let(:accounts) { [account, account1] }
28
+ let(:existing_email) { "user@example.com" }
29
+ let(:new_email) { "foo@example.com" }
30
+ let(:current_server) { "imap.example.com" }
31
+ let(:existing_password) { "password" }
32
+ let(:other_email) { "other@example.com" }
33
+ let(:other_existing_path) { "/other/existing/path" }
34
+
10
35
  let(:highline) { HIGHLINE }
11
36
  let(:store) { STORE }
12
37
 
@@ -43,28 +68,6 @@ describe Imap::Backup::Configuration::Account do
43
68
  let(:store) do
44
69
  instance_double(Imap::Backup::Configuration::Store, accounts: accounts)
45
70
  end
46
- let(:accounts) { [account, account1] }
47
- let(:account) do
48
- {
49
- username: existing_email,
50
- server: current_server,
51
- local_path: "/backup/path",
52
- folders: [{name: "my_folder"}],
53
- password: existing_password
54
- }
55
- end
56
- let(:account1) do
57
- {
58
- username: other_email,
59
- local_path: other_existing_path
60
- }
61
- end
62
- let(:existing_email) { "user@example.com" }
63
- let(:new_email) { "foo@example.com" }
64
- let(:current_server) { "imap.example.com" }
65
- let(:existing_password) { "password" }
66
- let(:other_email) { "other@example.com" }
67
- let(:other_existing_path) { "/other/existing/path" }
68
71
 
69
72
  before do
70
73
  allow(Kernel).to receive(:system)
@@ -139,6 +142,8 @@ describe Imap::Backup::Configuration::Account do
139
142
 
140
143
  describe "choosing 'modify email'" do
141
144
  before do
145
+ allow(account).to receive(:"username=")
146
+ allow(account).to receive(:"server=")
142
147
  allow(Imap::Backup::Configuration::Asker).
143
148
  to receive(:email) { new_email }
144
149
  subject.run
@@ -158,7 +163,7 @@ describe Imap::Backup::Configuration::Account do
158
163
  let(:current_server) { nil }
159
164
 
160
165
  it "sets a default server" do
161
- expect(account[:server]).to eq(expected)
166
+ expect(account).to have_received(:"server=").with(expected)
162
167
  end
163
168
  end
164
169
 
@@ -166,7 +171,7 @@ describe Imap::Backup::Configuration::Account do
166
171
  let(:current_server) { "" }
167
172
 
168
173
  it "sets a default server" do
169
- expect(account[:server]).to eq(expected)
174
+ expect(account).to have_received(:"server=").with(expected)
170
175
  end
171
176
  end
172
177
  end
@@ -183,17 +188,15 @@ describe Imap::Backup::Configuration::Account do
183
188
  end
184
189
 
185
190
  it "does not set a default server" do
186
- expect(account[:server]).to be_nil
191
+ expect(account).to_not have_received(:"server=")
187
192
  end
188
193
  end
189
194
  end
190
195
 
191
196
  context "when the email is new" do
192
197
  it "modifies the email address" do
193
- expect(account[:username]).to eq(new_email)
198
+ expect(account).to have_received(:"username=").with(new_email)
194
199
  end
195
-
196
- include_examples "it flags the account as modified"
197
200
  end
198
201
 
199
202
  context "when the email already exists" do
@@ -205,10 +208,8 @@ describe Imap::Backup::Configuration::Account do
205
208
  end
206
209
 
207
210
  it "doesn't set the email" do
208
- expect(account[:username]).to eq(existing_email)
211
+ expect(account.username).to eq(existing_email)
209
212
  end
210
-
211
- include_examples "it doesn't flag the account as modified"
212
213
  end
213
214
  end
214
215
 
@@ -216,6 +217,7 @@ describe Imap::Backup::Configuration::Account do
216
217
  let(:new_password) { "new_password" }
217
218
 
218
219
  before do
220
+ allow(account).to receive(:"password=")
219
221
  allow(Imap::Backup::Configuration::Asker).
220
222
  to receive(:password) { new_password }
221
223
  subject.run
@@ -224,20 +226,16 @@ describe Imap::Backup::Configuration::Account do
224
226
 
225
227
  context "when the user enters a password" do
226
228
  it "updates the password" do
227
- expect(account[:password]).to eq(new_password)
229
+ expect(account).to have_received(:"password=").with(new_password)
228
230
  end
229
-
230
- include_examples "it flags the account as modified"
231
231
  end
232
232
 
233
233
  context "when the user cancels" do
234
234
  let(:new_password) { nil }
235
235
 
236
236
  it "does nothing" do
237
- expect(account[:password]).to eq(existing_password)
237
+ expect(account.password).to eq(existing_password)
238
238
  end
239
-
240
- include_examples "it doesn't flag the account as modified"
241
239
  end
242
240
  end
243
241
 
@@ -245,6 +243,7 @@ describe Imap::Backup::Configuration::Account do
245
243
  let(:server) { "server" }
246
244
 
247
245
  before do
246
+ allow(account).to receive(:"server=")
248
247
  allow(highline).to receive(:ask).with("server: ") { server }
249
248
 
250
249
  subject.run
@@ -253,16 +252,15 @@ describe Imap::Backup::Configuration::Account do
253
252
  end
254
253
 
255
254
  it "updates the server" do
256
- expect(account[:server]).to eq(server)
255
+ expect(account).to have_received(:"server=").with(server)
257
256
  end
258
-
259
- include_examples "it flags the account as modified"
260
257
  end
261
258
 
262
259
  describe "choosing 'modify backup path'" do
263
260
  let(:new_backup_path) { "/new/path" }
264
261
 
265
262
  before do
263
+ allow(account).to receive(:"local_path=")
266
264
  @validator = nil
267
265
  allow(
268
266
  Imap::Backup::Configuration::Asker
@@ -275,7 +273,7 @@ describe Imap::Backup::Configuration::Account do
275
273
  end
276
274
 
277
275
  it "updates the path" do
278
- expect(account[:local_path]).to eq(new_backup_path)
276
+ expect(account).to have_received(:"local_path=").with(new_backup_path)
279
277
  end
280
278
 
281
279
  context "when the path is not used by other backups" do
@@ -293,8 +291,6 @@ describe Imap::Backup::Configuration::Account do
293
291
  # rubocop:enable RSpec/InstanceVariable
294
292
  end
295
293
  end
296
-
297
- include_examples "it flags the account as modified"
298
294
  end
299
295
 
300
296
  describe "choosing 'choose backup folders'" do
@@ -333,6 +329,7 @@ describe Imap::Backup::Configuration::Account do
333
329
  let(:confirmed) { true }
334
330
 
335
331
  before do
332
+ allow(account).to receive(:mark_for_deletion!)
336
333
  allow(highline).to receive(:agree) { confirmed }
337
334
  subject.run
338
335
  catch :done do
@@ -345,13 +342,17 @@ describe Imap::Backup::Configuration::Account do
345
342
  end
346
343
 
347
344
  context "when the user confirms deletion" do
348
- include_examples "it flags the account to be deleted"
345
+ it "flags the account to be deleted" do
346
+ expect(account).to have_received(:mark_for_deletion!)
347
+ end
349
348
  end
350
349
 
351
350
  context "without confirmation" do
352
351
  let(:confirmed) { false }
353
352
 
354
- include_examples "it doesn't flag the account to be deleted"
353
+ it "doesn't flag the account to be deleted" do
354
+ expect(account).to_not have_received(:mark_for_deletion!)
355
+ end
355
356
  end
356
357
  end
357
358
  end
@@ -9,7 +9,14 @@ describe Imap::Backup::Configuration::FolderChooser do
9
9
  Imap::Backup::Account::Connection, folders: connection_folders
10
10
  )
11
11
  end
12
- let(:account) { {folders: []} }
12
+ let(:account) do
13
+ instance_double(
14
+ Imap::Backup::Account,
15
+ folders: account_folders,
16
+ "folders=": nil
17
+ )
18
+ end
19
+ let(:account_folders) { [] }
13
20
  let(:connection_folders) { [] }
14
21
  let!(:highline_streams) { prepare_highline }
15
22
  let(:input) { highline_streams[0] }
@@ -36,7 +43,7 @@ describe Imap::Backup::Configuration::FolderChooser do
36
43
  end
37
44
 
38
45
  describe "folder listing" do
39
- let(:account) { {folders: [{name: "my_folder"}]} }
46
+ let(:account_folders) { [{name: "my_folder"}]}
40
47
  let(:connection_folders) do
41
48
  # N.B. my_folder is already backed up
42
49
  %w(my_folder another_folder)
@@ -62,10 +69,9 @@ describe Imap::Backup::Configuration::FolderChooser do
62
69
  end
63
70
 
64
71
  specify "are added to the account" do
65
- expect(account[:folders]).to include(name: "another_folder")
72
+ expect(account).to have_received(:"folders=").
73
+ with([{name: "my_folder"}, {name: "another_folder"}])
66
74
  end
67
-
68
- include_examples "it flags the account as modified"
69
75
  end
70
76
 
71
77
  context "when removing folders" do
@@ -76,22 +82,16 @@ describe Imap::Backup::Configuration::FolderChooser do
76
82
  end
77
83
 
78
84
  specify "are removed from the account" do
79
- expect(account[:folders]).to_not include(name: "my_folder")
85
+ expect(account).to have_received(:"folders=").with([])
80
86
  end
81
-
82
- include_examples "it flags the account as modified"
83
87
  end
84
88
  end
85
89
 
86
90
  context "with missing remote folders" do
87
- let(:account) do
88
- {folders: [{name: "on_server"}, {name: "not_on_server"}]}
89
- end
90
- let(:connection_folders) do
91
- [
92
- instance_double(Imap::Backup::Account::Folder, name: "on_server")
93
- ]
91
+ let(:account_folders) do
92
+ [{name: "on_server"}, {name: "not_on_server"}]
94
93
  end
94
+ let(:connection_folders) { ["on_server"] }
95
95
 
96
96
  before do
97
97
  allow(Kernel).to receive(:puts)
@@ -99,10 +99,9 @@ describe Imap::Backup::Configuration::FolderChooser do
99
99
  end
100
100
 
101
101
  specify "are removed from the account" do
102
- expect(account[:folders]).to_not include(name: "not_on_server")
102
+ expect(account).to have_received(:"folders=").
103
+ with([{name: "on_server"}])
103
104
  end
104
-
105
- include_examples "it flags the account as modified"
106
105
  end
107
106
 
108
107
  context "when folders are not available" do
@@ -3,8 +3,31 @@ describe Imap::Backup::Configuration::Setup do
3
3
 
4
4
  subject { described_class.new }
5
5
 
6
- let(:normal) { {username: "account@example.com"} }
7
- let(:accounts) { [normal] }
6
+ let(:normal_account) do
7
+ instance_double(
8
+ Imap::Backup::Account,
9
+ username: "account@example.com",
10
+ marked_for_deletion?: false,
11
+ modified?: false
12
+ )
13
+ end
14
+ let(:modified_account) do
15
+ instance_double(
16
+ Imap::Backup::Account,
17
+ username: "modified@example.com",
18
+ marked_for_deletion?: false,
19
+ modified?: true
20
+ )
21
+ end
22
+ let(:deleted_account) do
23
+ instance_double(
24
+ Imap::Backup::Account,
25
+ username: "delete@example.com",
26
+ marked_for_deletion?: true,
27
+ modified?: false
28
+ )
29
+ end
30
+ let(:accounts) { [normal_account] }
8
31
  let(:store) do
9
32
  instance_double(
10
33
  Imap::Backup::Configuration::Store,
@@ -63,9 +86,7 @@ describe Imap::Backup::Configuration::Setup do
63
86
  end
64
87
 
65
88
  describe "listing" do
66
- let(:accounts) { [normal, modified, deleted] }
67
- let(:modified) { {username: "modified@example.com", modified: true} }
68
- let(:deleted) { {username: "deleted@example.com", delete: true} }
89
+ let(:accounts) { [normal_account, modified_account, deleted_account] }
69
90
 
70
91
  before { subject.run }
71
92
 
@@ -98,7 +119,7 @@ describe Imap::Backup::Configuration::Setup do
98
119
  allow(Imap::Backup::Configuration::Asker).to receive(:email).
99
120
  with(no_args) { "new@example.com" }
100
121
  allow(Imap::Backup::Configuration::Account).to receive(:new).
101
- with(store, normal, anything) { account }
122
+ with(store, normal_account, anything) { account }
102
123
  end
103
124
 
104
125
  it "edits the account" do
@@ -134,19 +155,19 @@ describe Imap::Backup::Configuration::Setup do
134
155
  end
135
156
 
136
157
  it "sets username" do
137
- expect(accounts[1][:username]).to eq(added_email)
158
+ expect(accounts[1].username).to eq(added_email)
138
159
  end
139
160
 
140
161
  it "sets blank password" do
141
- expect(accounts[1][:password]).to eq("")
162
+ expect(accounts[1].password).to eq("")
142
163
  end
143
164
 
144
165
  it "sets local_path" do
145
- expect(accounts[1][:local_path]).to eq(local_path)
166
+ expect(accounts[1].local_path).to eq(local_path)
146
167
  end
147
168
 
148
169
  it "sets folders" do
149
- expect(accounts[1][:folders]).to eq([])
170
+ expect(accounts[1].folders).to eq([])
150
171
  end
151
172
 
152
173
  context "when the account is a GMail account" do
@@ -154,12 +175,12 @@ describe Imap::Backup::Configuration::Setup do
154
175
  let(:local_path) { "/base/path/new_gmail.com" }
155
176
 
156
177
  it "sets the server" do
157
- expect(accounts[1][:server]).to eq(gmail_imap_server)
178
+ expect(accounts[1].server).to eq(gmail_imap_server)
158
179
  end
159
180
  end
160
181
 
161
182
  it "doesn't flag the unedited account as modified" do
162
- expect(accounts[1][:modified]).to be_nil
183
+ expect(accounts[1].modified?).to be_falsey
163
184
  end
164
185
  end
165
186
 
@@ -8,10 +8,12 @@ describe Imap::Backup::Configuration::Store do
8
8
  let(:file_path) { File.join(directory, "/config.json") }
9
9
  let(:file_exists) { true }
10
10
  let(:directory_exists) { true }
11
- let(:data) { {debug: debug, accounts: accounts} }
12
11
  let(:debug) { true }
13
- let(:accounts) { [] }
14
12
  let(:configuration) { data.to_json }
13
+ let(:data) { {debug: debug, accounts: accounts.map(&:to_h)} }
14
+ let(:accounts) { [account1, account2] }
15
+ let(:account1) { Imap::Backup::Account.new({username: "username1"}) }
16
+ let(:account2) { Imap::Backup::Account.new({username: "username2"}) }
15
17
 
16
18
  before do
17
19
  stub_const(
@@ -48,8 +50,8 @@ describe Imap::Backup::Configuration::Store do
48
50
  end
49
51
 
50
52
  describe "#modified?" do
51
- context "with accounts flagged 'modified'" do
52
- let(:accounts) { [{name: "foo", modified: true}] }
53
+ context "with modified accounts" do
54
+ before { subject.accounts[0].username = "changed" }
53
55
 
54
56
  it "is true" do
55
57
  expect(subject.modified?).to be_truthy
@@ -57,7 +59,7 @@ describe Imap::Backup::Configuration::Store do
57
59
  end
58
60
 
59
61
  context "with accounts flagged 'delete'" do
60
- let(:accounts) { [{name: "foo", delete: true}] }
62
+ before { subject.accounts[0].mark_for_deletion! }
61
63
 
62
64
  it "is true" do
63
65
  expect(subject.modified?).to be_truthy
@@ -65,8 +67,6 @@ describe Imap::Backup::Configuration::Store do
65
67
  end
66
68
 
67
69
  context "without accounts flagged 'modified'" do
68
- let(:accounts) { [{name: "foo"}] }
69
-
70
70
  it "is false" do
71
71
  expect(subject.modified?).to be_falsey
72
72
  end
@@ -156,19 +156,14 @@ describe Imap::Backup::Configuration::Store do
156
156
  subject.save
157
157
  end
158
158
 
159
- context "when accounts are modified" do
160
- let(:accounts) { [{name: "foo", modified: true}] }
161
-
162
- before { subject.save }
163
-
164
- it "skips the 'modified' flag" do
165
- expected = Marshal.load(Marshal.dump(data))
166
- expected[:accounts][0].delete(:modified)
159
+ it "uses the Account#to_h method" do
160
+ allow(subject.accounts[0]).to receive(:to_h) { "Account1" }
161
+ allow(subject.accounts[1]).to receive(:to_h) { "Account2" }
167
162
 
168
- expect(JSON).to receive(:pretty_generate).with(expected)
163
+ expect(JSON).to receive(:pretty_generate).
164
+ with(hash_including({accounts: ["Account1", "Account2"]}))
169
165
 
170
- subject.save
171
- end
166
+ subject.save
172
167
  end
173
168
 
174
169
  context "when accounts are to be deleted" do
@@ -179,11 +174,15 @@ describe Imap::Backup::Configuration::Store do
179
174
  ]
180
175
  end
181
176
 
182
- it "does not save them" do
183
- expected = Marshal.load(Marshal.dump(data))
184
- expected[:accounts].pop
177
+ before do
178
+ allow(subject.accounts[0]).to receive(:to_h) { "Account1" }
179
+ allow(subject.accounts[1]).to receive(:to_h) { "Account2" }
180
+ subject.accounts[0].mark_for_deletion!
181
+ end
185
182
 
186
- expect(JSON).to receive(:pretty_generate).with(expected)
183
+ it "does not save them" do
184
+ expect(JSON).to receive(:pretty_generate).
185
+ with(hash_including({accounts: ["Account2"]}))
187
186
 
188
187
  subject.save
189
188
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.5
4
+ version: 4.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
@@ -237,7 +237,6 @@ files:
237
237
  - spec/spec_helper.rb
238
238
  - spec/support/fixtures.rb
239
239
  - spec/support/higline_test_helpers.rb
240
- - spec/support/shared_examples/account_flagging.rb
241
240
  - spec/support/silence_logging.rb
242
241
  - spec/unit/email/mboxrd/message_spec.rb
243
242
  - spec/unit/email/provider/apple_mail_spec.rb
@@ -316,7 +315,6 @@ test_files:
316
315
  - spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb
317
316
  - spec/unit/imap/backup/uploader_spec.rb
318
317
  - spec/support/fixtures.rb
319
- - spec/support/shared_examples/account_flagging.rb
320
318
  - spec/support/higline_test_helpers.rb
321
319
  - spec/support/silence_logging.rb
322
320
  - spec/gather_rspec_coverage.rb
@@ -1,23 +0,0 @@
1
- shared_examples "it flags the account as modified" do
2
- it "flags that the account has changed" do
3
- expect(account[:modified]).to be_truthy
4
- end
5
- end
6
-
7
- shared_examples "it doesn't flag the account as modified" do
8
- it "does not flag that the account has changed" do
9
- expect(account[:modified]).to be_falsey
10
- end
11
- end
12
-
13
- shared_examples "it flags the account to be deleted" do
14
- it "flags that the account is to be deleted" do
15
- expect(account[:delete]).to be_truthy
16
- end
17
- end
18
-
19
- shared_examples "it doesn't flag the account to be deleted" do
20
- it "does not flags that the account is to be deleted" do
21
- expect(account[:delete]).to be_falsey
22
- end
23
- end