Skip to content

Commit bf7ee01

Browse files
committed
Ensure version 2->3 migration happens before any .imap access
The delegators were allowing access to the metadata before it was migrated from the legacy version. This meant that the list of UIDs was empty. The second problem was that the Serializer::Imap instance was not reloaded. This meant that it continued not be '#valid?'. For this reason, after version migration both the '.imap' and '.mbox' files were deleted. Now, the migration happens on the first attempt at access to the metadata and the fresh metadata is then loaded.
1 parent f19dd8a commit bf7ee01

File tree

3 files changed

+95
-6
lines changed

3 files changed

+95
-6
lines changed

lib/imap/backup/serializer.rb

+35-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,41 @@ def self.folder_path_for(path:, folder:)
2828
extend Forwardable
2929

3030
def_delegator :mbox, :pathname, :mbox_pathname
31-
def_delegators :imap, :get, :messages, :uid_validity, :uids, :update_uid
31+
32+
# Get message metadata
33+
# @param uid [Integer] a message UID
34+
# @return [Serializer::Message]
35+
def get(uid)
36+
validate!
37+
imap.get(uid)
38+
end
39+
40+
# @return [Array<Hash>]
41+
def messages
42+
validate!
43+
imap.messages
44+
end
45+
46+
# @return [Integer] the UID validity for the folder
47+
def uid_validity
48+
validate!
49+
imap.uid_validity
50+
end
51+
52+
# @return [Array<Integer>] The uids of all messages
53+
def uids
54+
validate!
55+
imap.uids
56+
end
57+
58+
# Update a message's metadata, replacing its UID
59+
# @param old [Integer] the existing message UID
60+
# @param new [Integer] the new UID to apply to the message
61+
# @return [void]
62+
def update_uid(old, new)
63+
validate!
64+
imap.update_uid(old, new)
65+
end
3266

3367
# @return [String] a folder name
3468
attr_reader :folder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require "features/helper"
2+
3+
RSpec.describe "imap-backup migrate: avoid regression in migrating legacy backups",
4+
:container, type: :aruba do
5+
def overwrite_metadata_with_old_version(email, folder)
6+
content = imap_parsed(email, folder)
7+
uids = content[:messages].map { |m| m[:uid] }
8+
uid_validity = content[:uid_validity]
9+
old_metadata = {version: 2, uids: uids, uid_validity: uid_validity}
10+
path = imap_path(email, folder)
11+
File.open(path, "w") { |f| f.write(JSON.pretty_generate(old_metadata)) }
12+
end
13+
14+
let(:email) { "me@example.com" }
15+
let(:folder) { "migrate-folder" }
16+
let(:source_account) do
17+
{
18+
username: email,
19+
local_path: File.join(config_path, email.gsub("@", "_"))
20+
}
21+
end
22+
let(:destination_account) { test_server_connection_parameters }
23+
let(:destination_server) { test_server }
24+
let(:config_options) { {accounts: [source_account, destination_account]} }
25+
26+
let!(:setup) do
27+
test_server.warn_about_non_default_folders
28+
create_config(**config_options)
29+
append_local(
30+
email: email, folder: folder, subject: "Ciao", flags: [:Draft, :$CUSTOM]
31+
)
32+
overwrite_metadata_with_old_version(email, folder)
33+
end
34+
35+
after do
36+
destination_server.delete_folder folder
37+
destination_server.disconnect
38+
end
39+
40+
it "copies emails to the destination account" do
41+
run_command_and_stop "imap-backup migrate #{email} #{destination_account[:username]}"
42+
43+
messages = test_server.folder_messages(folder)
44+
expected = <<~MESSAGE.gsub("\n", "\r\n")
45+
From: sender@example.com
46+
Subject: Ciao
47+
48+
body
49+
50+
MESSAGE
51+
expect(messages[0]["BODY[]"]).to eq(expected)
52+
end
53+
end

spec/unit/serializer_spec.rb

+7-5
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,23 @@
3535
let(:imap_valid) { false }
3636

3737
it "deletes the imap file" do
38-
expect(imap).to have_received(:delete)
38+
expect(imap).to have_received(:delete).at_least(:once)
3939
end
4040

4141
it "deletes the mbox file" do
42-
expect(mbox).to have_received(:delete)
42+
expect(mbox).to have_received(:delete).at_least(:once)
4343
end
4444
end
4545

4646
context "when the mbox file is not valid" do
4747
let(:mbox_valid) { false }
4848

4949
it "deletes the imap file" do
50-
expect(imap).to have_received(:delete)
50+
expect(imap).to have_received(:delete).at_least(:once)
5151
end
5252

5353
it "deletes the mbox file" do
54-
expect(mbox).to have_received(:delete)
54+
expect(mbox).to have_received(:delete).at_least(:once)
5555
end
5656
end
5757
end
@@ -309,6 +309,7 @@ module Imap::Backup
309309
Serializer::Imap, "Old Imap",
310310
uid_validity: 1,
311311
uids: [1],
312+
valid?: true,
312313
get: message,
313314
delete: nil,
314315
folder_path: "existing/imap"
@@ -318,7 +319,8 @@ module Imap::Backup
318319
instance_double(
319320
Serializer::Mbox, "Old Mbox",
320321
delete: nil,
321-
folder_path: "existing/mbox"
322+
folder_path: "existing/mbox",
323+
valid?: true
322324
)
323325
end
324326
let(:imap) do

0 commit comments

Comments
 (0)