imap-backup 4.0.5 → 4.0.6

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 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