imap-backup 6.0.0.rc2 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/imap-backup.gemspec +5 -1
  3. data/lib/cli_coverage.rb +11 -11
  4. data/lib/email/provider/base.rb +2 -0
  5. data/lib/email/provider/unknown.rb +2 -0
  6. data/lib/email/provider.rb +2 -0
  7. data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
  8. data/lib/imap/backup/account/connection/client_factory.rb +54 -0
  9. data/lib/imap/backup/account/connection/folder_names.rb +26 -0
  10. data/lib/imap/backup/account/connection.rb +11 -95
  11. data/lib/imap/backup/account/folder.rb +9 -6
  12. data/lib/imap/backup/account.rb +3 -5
  13. data/lib/imap/backup/cli/backup.rb +1 -3
  14. data/lib/imap/backup/cli/helpers.rb +24 -22
  15. data/lib/imap/backup/cli/local.rb +20 -13
  16. data/lib/imap/backup/cli/migrate.rb +4 -10
  17. data/lib/imap/backup/cli/restore.rb +8 -7
  18. data/lib/imap/backup/cli/setup.rb +10 -8
  19. data/lib/imap/backup/cli/stats.rb +78 -0
  20. data/lib/imap/backup/cli/status.rb +2 -2
  21. data/lib/imap/backup/cli/utils.rb +4 -6
  22. data/lib/imap/backup/cli.rb +24 -3
  23. data/lib/imap/backup/configuration.rb +9 -11
  24. data/lib/imap/backup/downloader.rb +48 -30
  25. data/lib/imap/backup/migrator.rb +5 -5
  26. data/lib/imap/backup/sanitizer.rb +3 -2
  27. data/lib/imap/backup/serializer/appender.rb +49 -0
  28. data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
  29. data/lib/imap/backup/serializer/unused_name_finder.rb +27 -0
  30. data/lib/imap/backup/serializer.rb +24 -78
  31. data/lib/imap/backup/setup/account/header.rb +75 -0
  32. data/lib/imap/backup/setup/account.rb +14 -91
  33. data/lib/imap/backup/setup/asker.rb +4 -15
  34. data/lib/imap/backup/setup/backup_path.rb +41 -0
  35. data/lib/imap/backup/setup/email.rb +45 -0
  36. data/lib/imap/backup/setup/folder_chooser.rb +3 -3
  37. data/lib/imap/backup/setup/helpers.rb +1 -1
  38. data/lib/imap/backup/setup.rb +5 -4
  39. data/lib/imap/backup/thunderbird/mailbox_exporter.rb +39 -20
  40. data/lib/imap/backup/uploader.rb +46 -8
  41. data/lib/imap/backup/utils.rb +1 -1
  42. data/lib/imap/backup/version.rb +1 -1
  43. data/lib/imap/backup.rb +0 -1
  44. metadata +31 -134
  45. data/spec/features/backup_spec.rb +0 -100
  46. data/spec/features/configuration/minimal_configuration.rb +0 -15
  47. data/spec/features/configuration/missing_configuration.rb +0 -14
  48. data/spec/features/folders_spec.rb +0 -36
  49. data/spec/features/helper.rb +0 -2
  50. data/spec/features/local/list_accounts_spec.rb +0 -12
  51. data/spec/features/local/list_emails_spec.rb +0 -21
  52. data/spec/features/local/list_folders_spec.rb +0 -21
  53. data/spec/features/local/show_an_email_spec.rb +0 -34
  54. data/spec/features/migrate_spec.rb +0 -35
  55. data/spec/features/remote/list_account_folders_spec.rb +0 -16
  56. data/spec/features/restore_spec.rb +0 -162
  57. data/spec/features/status_spec.rb +0 -43
  58. data/spec/features/support/aruba.rb +0 -78
  59. data/spec/features/support/backup_directory.rb +0 -43
  60. data/spec/features/support/email_server.rb +0 -110
  61. data/spec/features/support/shared/connection_context.rb +0 -14
  62. data/spec/features/support/shared/message_fixtures.rb +0 -16
  63. data/spec/fixtures/connection.yml +0 -7
  64. data/spec/spec_helper.rb +0 -15
  65. data/spec/support/fixtures.rb +0 -11
  66. data/spec/support/higline_test_helpers.rb +0 -8
  67. data/spec/support/silence_logging.rb +0 -7
  68. data/spec/unit/email/mboxrd/message_spec.rb +0 -177
  69. data/spec/unit/email/provider/apple_mail_spec.rb +0 -7
  70. data/spec/unit/email/provider/base_spec.rb +0 -11
  71. data/spec/unit/email/provider/fastmail_spec.rb +0 -7
  72. data/spec/unit/email/provider/gmail_spec.rb +0 -7
  73. data/spec/unit/email/provider_spec.rb +0 -27
  74. data/spec/unit/imap/backup/account/connection_spec.rb +0 -433
  75. data/spec/unit/imap/backup/account/folder_spec.rb +0 -261
  76. data/spec/unit/imap/backup/account_spec.rb +0 -246
  77. data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -58
  78. data/spec/unit/imap/backup/cli/backup_spec.rb +0 -19
  79. data/spec/unit/imap/backup/cli/folders_spec.rb +0 -39
  80. data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
  81. data/spec/unit/imap/backup/cli/local_spec.rb +0 -100
  82. data/spec/unit/imap/backup/cli/migrate_spec.rb +0 -80
  83. data/spec/unit/imap/backup/cli/restore_spec.rb +0 -67
  84. data/spec/unit/imap/backup/cli/setup_spec.rb +0 -17
  85. data/spec/unit/imap/backup/cli/utils_spec.rb +0 -125
  86. data/spec/unit/imap/backup/cli_spec.rb +0 -93
  87. data/spec/unit/imap/backup/client/apple_mail_spec.rb +0 -9
  88. data/spec/unit/imap/backup/client/default_spec.rb +0 -22
  89. data/spec/unit/imap/backup/configuration_spec.rb +0 -238
  90. data/spec/unit/imap/backup/downloader_spec.rb +0 -96
  91. data/spec/unit/imap/backup/logger_spec.rb +0 -48
  92. data/spec/unit/imap/backup/migrator_spec.rb +0 -58
  93. data/spec/unit/imap/backup/sanitizer_spec.rb +0 -42
  94. data/spec/unit/imap/backup/serializer/directory_spec.rb +0 -37
  95. data/spec/unit/imap/backup/serializer/imap_spec.rb +0 -218
  96. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +0 -45
  97. data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -101
  98. data/spec/unit/imap/backup/serializer_spec.rb +0 -296
  99. data/spec/unit/imap/backup/setup/account_spec.rb +0 -461
  100. data/spec/unit/imap/backup/setup/asker_spec.rb +0 -137
  101. data/spec/unit/imap/backup/setup/connection_tester_spec.rb +0 -51
  102. data/spec/unit/imap/backup/setup/folder_chooser_spec.rb +0 -146
  103. data/spec/unit/imap/backup/setup/helpers_spec.rb +0 -15
  104. data/spec/unit/imap/backup/setup_spec.rb +0 -301
  105. data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +0 -116
  106. data/spec/unit/imap/backup/uploader_spec.rb +0 -54
  107. data/spec/unit/imap/backup/utils_spec.rb +0 -92
  108. data/spec/unit/retry_on_error_spec.rb +0 -34
@@ -1,162 +0,0 @@
1
- require "features/helper"
2
-
3
- RSpec.describe "restore", type: :aruba, docker: true do
4
- include_context "imap-backup connection"
5
- include_context "message-fixtures"
6
-
7
- let(:local_backup_path) { File.expand_path("~/backup") }
8
- let(:folder) { "my-stuff" }
9
- let(:messages_as_mbox) do
10
- message_as_mbox_entry(msg1) + message_as_mbox_entry(msg2)
11
- end
12
- let(:messages_as_server_messages) do
13
- [message_as_server_message(msg1), message_as_server_message(msg2)]
14
- end
15
- let(:message_uids) { [msg1[:uid], msg2[:uid]] }
16
- let(:existing_imap_content) { imap_data(uid_validity, message_uids).to_json }
17
- let(:uid_validity) { 1234 }
18
-
19
- let!(:pre) {}
20
- let!(:setup) do
21
- create_directory local_backup_path
22
- File.write(imap_path(folder), existing_imap_content)
23
- File.write(mbox_path(folder), messages_as_mbox)
24
- create_config(accounts: [account.to_h])
25
-
26
- run_command_and_stop("imap-backup restore #{account.username}")
27
- end
28
- let(:cleanup) do
29
- server_delete_folder folder
30
- end
31
-
32
- after { cleanup }
33
-
34
- context "when the folder doesn't exist" do
35
- it "restores messages" do
36
- messages = server_messages(folder).map { |m| server_message_to_body(m) }
37
- expect(messages).to eq(messages_as_server_messages)
38
- end
39
-
40
- it "updates local uids to match the new server ones" do
41
- updated_imap_content = imap_parsed(folder)
42
- expect(server_uids(folder)).to eq(updated_imap_content[:uids])
43
- end
44
-
45
- it "sets the backup uid_validity to match the new folder" do
46
- updated_imap_content = imap_parsed(folder)
47
- expect(updated_imap_content[:uid_validity]).
48
- to eq(server_uid_validity(folder))
49
- end
50
- end
51
-
52
- context "when the folder exists" do
53
- let(:email3) { send_email folder, msg3 }
54
-
55
- context "when the uid_validity matches" do
56
- let(:pre) do
57
- server_create_folder folder
58
- email3
59
- uid_validity
60
- end
61
- let(:messages_as_server_messages) do
62
- [
63
- message_as_server_message(msg3),
64
- message_as_server_message(msg1),
65
- message_as_server_message(msg2)
66
- ]
67
- end
68
- let(:uid_validity) { server_uid_validity(folder) }
69
-
70
- it "appends to the existing folder" do
71
- messages = server_messages(folder).map { |m| server_message_to_body(m) }
72
- expect(messages).to eq(messages_as_server_messages)
73
- end
74
- end
75
-
76
- context "when the uid_validity doesn't match" do
77
- context "when the folder is empty" do
78
- let(:pre) do
79
- server_create_folder folder
80
- end
81
-
82
- it "sets the backup uid_validity to match the folder" do
83
- updated_imap_content = imap_parsed(folder)
84
- expect(updated_imap_content[:uid_validity]).
85
- to eq(server_uid_validity(folder))
86
- end
87
-
88
- it "uploads to the new folder" do
89
- messages = server_messages(folder).map do |m|
90
- server_message_to_body(m)
91
- end
92
- expect(messages).to eq(messages_as_server_messages)
93
- end
94
- end
95
-
96
- context "when the folder has content" do
97
- let(:new_folder) { "#{folder}-#{uid_validity}" }
98
- let(:cleanup) do
99
- server_delete_folder new_folder
100
- super()
101
- end
102
-
103
- let(:pre) do
104
- server_create_folder folder
105
- email3
106
- end
107
-
108
- it "renames the backup" do
109
- expect(mbox_content(new_folder)).to eq(messages_as_mbox)
110
- end
111
-
112
- it "leaves the existing folder as is" do
113
- messages = server_messages(folder).map do |m|
114
- server_message_to_body(m)
115
- end
116
- expect(messages).to eq([message_as_server_message(msg3)])
117
- end
118
-
119
- it "creates the new folder" do
120
- expect(server_folders.map(&:name)).to include(new_folder)
121
- end
122
-
123
- it "sets the backup uid_validity to match the new folder" do
124
- updated_imap_content = imap_parsed(new_folder)
125
- expect(updated_imap_content[:uid_validity]).
126
- to eq(server_uid_validity(new_folder))
127
- end
128
-
129
- it "uploads to the new folder" do
130
- messages = server_messages(new_folder).map do |m|
131
- server_message_to_body(m)
132
- end
133
- expect(messages).to eq(messages_as_server_messages)
134
- end
135
- end
136
- end
137
- end
138
-
139
- context "when non-Unicode encodings are used" do
140
- let(:server_message) do
141
- message_as_server_message(msg_iso8859)
142
- end
143
- let(:messages_as_mbox) do
144
- message_as_mbox_entry(msg_iso8859)
145
- end
146
- let(:message_uids) { [uid_iso8859] }
147
- let(:uid_validity) { server_uid_validity(folder) }
148
-
149
- let(:pre) do
150
- server_create_folder folder
151
- uid_validity
152
- end
153
-
154
- it "maintains encodings" do
155
- message =
156
- server_messages(folder).
157
- first["BODY[]"]
158
-
159
- expect(message).to eq(server_message)
160
- end
161
- end
162
- end
@@ -1,43 +0,0 @@
1
- require "features/helper"
2
- require "imap/backup/cli/status"
3
-
4
- RSpec.describe "status", type: :feature, docker: true do
5
- include_context "imap-backup connection"
6
- include_context "message-fixtures"
7
-
8
- context "when there are non-backed-up messages" do
9
- let(:options) do
10
- {accounts: "address@example.org"}
11
- end
12
- let(:folder) { "my-stuff" }
13
- let(:backup_folders) { [{name: folder}] }
14
- let(:email1) { send_email folder, msg1 }
15
- let(:output) { StringIO.new }
16
-
17
- before do
18
- allow(Imap::Backup::CLI::Accounts).to receive(:new) { [account] }
19
- server_create_folder folder
20
- email1
21
- end
22
-
23
- around do |example|
24
- stdout = $stdout
25
- $stdout = output
26
- example.run
27
- $stdout = stdout
28
- end
29
-
30
- after do
31
- FileUtils.rm_rf local_backup_path
32
- delete_emails folder
33
- server_delete_folder folder
34
- connection.disconnect
35
- end
36
-
37
- it "prints the number" do
38
- Imap::Backup::CLI::Status.new(options).run
39
-
40
- expect(output.string).to eq("address@example.org\nmy-stuff: 1\n")
41
- end
42
- end
43
- end
@@ -1,78 +0,0 @@
1
- require "aruba/rspec"
2
-
3
- require_relative "backup_directory"
4
- require "imap/backup/serializer/mbox"
5
-
6
- Aruba.configure do |config|
7
- config.home_directory = File.expand_path("./tmp/home")
8
- config.allow_absolute_paths = true
9
- end
10
-
11
- module ConfigurationHelpers
12
- def config_path
13
- File.expand_path("~/.imap-backup")
14
- end
15
-
16
- def create_config(accounts:, debug: false)
17
- pathname = File.join(config_path, "config.json")
18
- save_data = {
19
- version: Imap::Backup::Configuration::VERSION,
20
- accounts: accounts,
21
- debug: debug
22
- }
23
- FileUtils.mkdir_p config_path
24
- File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(save_data)) }
25
- FileUtils.chmod(0o600, pathname)
26
- end
27
- end
28
-
29
- module StoreHelpers
30
- def store_email(
31
- email:, folder:,
32
- uid: 1,
33
- from: "sender@example.com",
34
- subject: "The Subject",
35
- body: "body"
36
- )
37
- account = config.accounts.find { |a| a.username == email }
38
- raise "Account not found" if !account
39
- FileUtils.mkdir_p account.local_path
40
- serializer = Imap::Backup::Serializer.new(account.local_path, folder)
41
- serializer.force_uid_validity("42") if !serializer.uid_validity
42
- serialized = to_serialized(from: from, subject: subject, body: body)
43
- serializer.append uid, serialized
44
- end
45
-
46
- def to_serialized(from:, subject:, body:)
47
- body_and_headers = <<~BODY
48
- From: #{from}
49
- Subject: #{subject}
50
-
51
- #{body}
52
- BODY
53
- end
54
-
55
- def config
56
- Imap::Backup::Configuration.new(
57
- File.expand_path("~/.imap-backup/config.json")
58
- )
59
- end
60
- end
61
-
62
- RSpec.configure do |config|
63
- config.include ConfigurationHelpers, type: :aruba
64
- config.include StoreHelpers, type: :aruba
65
- config.include BackupDirectoryHelpers, type: :aruba
66
-
67
- config.before(:suite) do
68
- FileUtils.rm_rf "./tmp/home"
69
- end
70
-
71
- config.before(:example, type: :aruba) do
72
- set_environment_variable("COVERAGE", "aruba")
73
- end
74
-
75
- config.after do
76
- FileUtils.rm_rf "./tmp/home"
77
- end
78
- end
@@ -1,43 +0,0 @@
1
- module BackupDirectoryHelpers
2
- def message_as_mbox_entry(options)
3
- from = fixture("connection")[:username]
4
- subject = options[:subject]
5
- body = options[:body]
6
- body_and_headers = <<~BODY
7
- From: #{from}
8
- Subject: #{subject}
9
-
10
- #{body}
11
- BODY
12
-
13
- "From #{from}\n#{body_and_headers}\n"
14
- end
15
-
16
- def imap_data(uid_validity, uids)
17
- {
18
- version: 2,
19
- uid_validity: uid_validity,
20
- uids: uids
21
- }
22
- end
23
-
24
- def mbox_content(name)
25
- File.read(mbox_path(name))
26
- end
27
-
28
- def mbox_path(name)
29
- File.join(local_backup_path, "#{name}.mbox")
30
- end
31
-
32
- def imap_path(name)
33
- File.join(local_backup_path, "#{name}.imap")
34
- end
35
-
36
- def imap_content(name)
37
- File.read(imap_path(name))
38
- end
39
-
40
- def imap_parsed(name)
41
- JSON.parse(imap_content(name), symbolize_names: true)
42
- end
43
- end
@@ -1,110 +0,0 @@
1
- module EmailServerHelpers
2
- REQUESTED_ATTRIBUTES = ["BODY[]"].freeze
3
- DEFAULT_EMAIL = "address@example.org".freeze
4
-
5
- def send_email(folder, options)
6
- message = message_as_server_message(options)
7
- imap.append(folder, message, nil, nil)
8
- end
9
-
10
- def message_as_server_message(options)
11
- from = options[:from] || DEFAULT_EMAIL
12
- subject = options[:subject]
13
- body = options[:body]
14
-
15
- <<~MESSAGE.gsub("\n", "\r\n")
16
- From: #{from}
17
- Subject: #{subject}
18
-
19
- #{body}
20
-
21
- MESSAGE
22
- end
23
-
24
- def server_messages(folder)
25
- server_uids(folder).map do |uid|
26
- server_fetch_email(folder, uid)
27
- end
28
- end
29
-
30
- def server_message_to_body(message)
31
- message["BODY[]"]
32
- end
33
-
34
- def server_fetch_email(folder, uid)
35
- examine folder
36
-
37
- fetch_data_items = imap.uid_fetch([uid.to_i], REQUESTED_ATTRIBUTES)
38
- return nil if fetch_data_items.nil?
39
-
40
- fetch_data_item = fetch_data_items[0]
41
- fetch_data_item.attr
42
- end
43
-
44
- def delete_emails(folder)
45
- imap.select(folder)
46
- uids = imap.uid_search(["ALL"]).sort
47
- imap.store(1..uids.size, "+FLAGS", [:Deleted])
48
- imap.expunge
49
- end
50
-
51
- def examine(folder)
52
- imap.examine(folder)
53
- end
54
-
55
- def server_uids(folder)
56
- examine(folder)
57
- imap.uid_search(["ALL"]).sort
58
- end
59
-
60
- def server_uid_validity(folder)
61
- examine(folder)
62
- imap.responses["UIDVALIDITY"][0]
63
- end
64
-
65
- def server_folders
66
- imap.list(root, "*")
67
- end
68
-
69
- def root
70
- root_info = imap.list("", "")[0]
71
- root_info.name
72
- end
73
-
74
- def server_create_folder(folder)
75
- imap.create(folder)
76
- imap.disconnect
77
- @imap = nil
78
- end
79
-
80
- def server_rename_folder(from, to)
81
- imap.rename(from, to)
82
- imap.disconnect
83
- @imap = nil
84
- end
85
-
86
- def server_delete_folder(folder)
87
- imap.delete folder
88
- imap.disconnect
89
- rescue StandardError => e
90
- ensure
91
- @imap = nil
92
- end
93
-
94
- def imap
95
- @imap ||=
96
- begin
97
- connection = fixture("connection")
98
- imap = Net::IMAP.new(
99
- connection[:server], connection[:connection_options]
100
- )
101
- imap.login(connection[:username], connection[:password])
102
- imap
103
- end
104
- end
105
- end
106
-
107
- RSpec.configure do |config|
108
- config.include EmailServerHelpers, type: :aruba
109
- config.include EmailServerHelpers, type: :feature
110
- end
@@ -1,14 +0,0 @@
1
- shared_context "imap-backup connection" do
2
- let(:local_backup_path) { Dir.mktmpdir(nil, "tmp") }
3
- let(:default_connection) { fixture("connection") }
4
- let(:backup_folders) { nil }
5
- let(:account) do
6
- Imap::Backup::Account.new(
7
- default_connection.merge(
8
- local_path: local_backup_path,
9
- folders: backup_folders
10
- )
11
- )
12
- end
13
- let(:connection) { Imap::Backup::Account::Connection.new(account) }
14
- end
@@ -1,16 +0,0 @@
1
- shared_context "message-fixtures" do
2
- let(:uid1) { 123 }
3
- let(:uid2) { 345 }
4
- let(:uid3) { 567 }
5
- let(:uid_iso8859) { 890 }
6
- let(:msg1) { {uid: uid1, subject: "Test 1", body: "body 1\nHi"} }
7
- let(:msg2) { {uid: uid2, subject: "Test 2", body: "body 2"} }
8
- let(:msg3) { {uid: uid3, subject: "Test 3", body: "body 3"} }
9
- let(:msg_iso8859) do
10
- {
11
- uid: uid_iso8859,
12
- subject: "iso8859 Body",
13
- body: "Ma, perchè?".encode(Encoding::ISO_8859_1).force_encoding("binary")
14
- }
15
- end
16
- end
@@ -1,7 +0,0 @@
1
- :server: 'localhost'
2
- :username: 'address@example.org'
3
- :password: 'pass'
4
- :connection_options:
5
- :port: <%= ENV.fetch("DOCKER_IMAP_SERVER", 8993) %>
6
- :ssl:
7
- :verify_mode: 0
data/spec/spec_helper.rb DELETED
@@ -1,15 +0,0 @@
1
- require "rspec"
2
-
3
- $LOAD_PATH << File.expand_path("../lib", __dir__)
4
-
5
- support_glob = File.join(__dir__, "support", "**", "*.rb")
6
- Dir[support_glob].sort.each { |f| require f }
7
-
8
- require "simplecov"
9
-
10
- SimpleCov.command_name "RSpec tests"
11
-
12
- require "imap/backup"
13
- require "imap/backup/cli"
14
-
15
- silence_logging
@@ -1,11 +0,0 @@
1
- require "erb"
2
- require "yaml"
3
-
4
- def fixture(name)
5
- spec_root = File.expand_path("..", File.dirname(__FILE__))
6
- fixture_path = File.join(spec_root, "fixtures", "#{name}.yml")
7
- content = File.read(fixture_path)
8
- template = ERB.new(content)
9
- yaml = template.result(binding)
10
- YAML.safe_load(yaml, [Symbol])
11
- end
@@ -1,8 +0,0 @@
1
- module HighLineTestHelpers
2
- def prepare_highline
3
- @input = instance_double(IO, eof?: false, gets: "q\n")
4
- @output = StringIO.new
5
- Imap::Backup::Setup.highline = HighLine.new(@input, @output)
6
- [@input, @output]
7
- end
8
- end
@@ -1,7 +0,0 @@
1
- def silence_logging
2
- RSpec.configure do |config|
3
- config.before(:suite) do
4
- Imap::Backup::Logger.logger.level = Logger::UNKNOWN
5
- end
6
- end
7
- end
@@ -1,177 +0,0 @@
1
- msg_no_from = <<~NO_FROM
2
- Delivered-To: you@example.com
3
- From: example <www.example.com>
4
- To: FirstName LastName <you@example.com>
5
- Subject: Re: no subject
6
- NO_FROM
7
-
8
- msg_bad_from = <<~BAD_FROM
9
- Delivered-To: you@example.com
10
- from: "FirstName LastName (TEXT)" <"TEXT*" <no-reply@example.com>>
11
- To: FirstName LastName <you@example.com>
12
- Subject: Re: no subject
13
- BAD_FROM
14
-
15
- msg_no_from_but_return_path = <<~RETURN_PATH
16
- Delivered-To: you@example.com
17
- From: example <www.example.com>
18
- To: FirstName LastName <you@example.com>
19
- Return-Path: <me@example.com>
20
- Subject: Re: no subject
21
- RETURN_PATH
22
-
23
- msg_no_from_but_sender = <<~NOT_SENDER
24
- Delivered-To: you@example.com
25
- To: FirstName LastName <you@example.com>
26
- Subject: Re: no subject
27
- Sender: FistName LastName <me@example.com>
28
- NOT_SENDER
29
-
30
- describe Email::Mboxrd::Message do
31
- subject { described_class.new(message_body) }
32
-
33
- let(:from) { "me@example.com" }
34
- let(:date) { DateTime.new(2012, 12, 13, 18, 23, 45) }
35
- let(:message_body) do
36
- instance_double(String, clone: cloned_message_body, force_encoding: nil)
37
- end
38
- let(:cloned_message_body) do
39
- "Foo\nBar\nFrom at the beginning of the line\n>>From quoted"
40
- end
41
- let(:msg_good) do
42
- <<~GOOD
43
- Delivered-To: you@example.com
44
- From: Foo <foo@example.com>
45
- To: FirstName LastName <you@example.com>
46
- Date: #{date.rfc822}
47
- Subject: Re: no subject
48
- GOOD
49
- end
50
-
51
- let(:msg_bad_date) do
52
- <<~BAD
53
- Delivered-To: you@example.com
54
- From: Foo <foo@example.com>
55
- To: FirstName LastName <you@example.com>
56
- Date: Mon,5 May 2014 08:97:99 GMT
57
- Subject: Re: no subject
58
- BAD
59
- end
60
-
61
- describe ".from_serialized" do
62
- let(:serialized_message) { "From foo@a.com\n#{imap_message}" }
63
- let(:imap_message) { "Delivered-To: me@example.com\nFrom Me\n" }
64
- let!(:result) { described_class.from_serialized(serialized_message) }
65
-
66
- it "returns the message" do
67
- expect(result).to be_a(described_class)
68
- end
69
-
70
- it "removes one level of > before From" do
71
- expect(result.supplied_body).to eq(imap_message)
72
- end
73
- end
74
-
75
- describe "#to_serialized" do
76
- let(:mail) { instance_double(Mail::Message, from: [from], date: date) }
77
-
78
- before do
79
- allow(Mail).to receive(:new).with(cloned_message_body) { mail }
80
- end
81
-
82
- it "adds a 'From ' line at the start" do
83
- expected = "From #{from} #{date.asctime}\n"
84
- expect(subject.to_serialized).to start_with(expected)
85
- end
86
-
87
- it "replaces existing 'From ' with '>From '" do
88
- expect(subject.to_serialized).to include("\n>From at the beginning")
89
- end
90
-
91
- it "appends > before '>+From '" do
92
- expect(subject.to_serialized).to include("\n>>>From quoted")
93
- end
94
-
95
- context "when date is missing" do
96
- let(:date) { nil }
97
-
98
- it "does no fail" do
99
- expect { subject.to_serialized }.to_not raise_error
100
- end
101
- end
102
- end
103
-
104
- describe "From" do
105
- before do
106
- # call original for these tests because we want to test the behaviour of
107
- # class-under-test given different behaviour of the Mail parser
108
- allow(Mail).to receive(:new).and_call_original
109
- end
110
-
111
- context "when original message 'from' is missing" do
112
- let(:message_body) { msg_no_from }
113
-
114
- it "'from' is empty string" do
115
- expect(subject.to_serialized).to start_with("From \n")
116
- end
117
- end
118
-
119
- context "when original message 'from' is not a well-formed address" do
120
- let(:message_body) { msg_bad_from }
121
-
122
- it "'from' is empty string" do
123
- expect(subject.to_serialized).to start_with("From \n")
124
- end
125
- end
126
-
127
- context "when original message 'from' is nil and " \
128
- "'envelope from' is nil and 'return path' is available" do
129
- let(:message_body) { msg_no_from_but_return_path }
130
-
131
- it "'return path' is used as 'from'" do
132
- expect(subject.to_serialized).to start_with("From #{from}\n")
133
- end
134
- end
135
-
136
- context "with no from and a 'Sender'" do
137
- let(:message_body) { msg_no_from_but_sender }
138
-
139
- it "Sender is used as 'from'" do
140
- expect(subject.to_serialized).to start_with("From #{from}\n")
141
- end
142
- end
143
- end
144
-
145
- describe "#date" do
146
- let(:message_body) { msg_good }
147
-
148
- it "returns the date" do
149
- expect(subject.date).to eq(date)
150
- end
151
-
152
- context "with incorrect minutes and seconds" do
153
- let(:message_body) { msg_bad_date }
154
-
155
- it "returns nil" do
156
- expect(subject.date).to be_nil
157
- end
158
- end
159
- end
160
-
161
- describe "#imap_body" do
162
- let(:message_body) { "Ciao" }
163
-
164
- it "returns the supplied body" do
165
- expect(subject.imap_body).to eq(message_body)
166
- end
167
-
168
- context "when newlines are not IMAP standard" do
169
- let(:message_body) { "Ciao\nHello" }
170
- let(:corrected) { "Ciao\r\nHello" }
171
-
172
- it "corrects them" do
173
- expect(subject.imap_body).to eq(corrected)
174
- end
175
- end
176
- end
177
- end