Since the diaspora_federation gem doesn't try to access host-meta anymore, there is no need to create stubs for it anymore.
458 lines
18 KiB
Ruby
458 lines
18 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "integration/federation/federation_helper"
|
|
require "integration/archive_shared"
|
|
|
|
describe MigrationService do
|
|
let(:old_pod_hostname) { "originalhomepod.tld" }
|
|
let(:archive_author) { "previous_username@#{old_pod_hostname}" }
|
|
let(:archive_private_key) { OpenSSL::PKey::RSA.generate(1024) }
|
|
let(:contact1_diaspora_id) { known_contact_person.diaspora_handle }
|
|
let(:contact2_diaspora_id) { Fabricate.sequence(:diaspora_id) }
|
|
let(:unknown_subscription_guid) { UUID.generate(:compact) }
|
|
let(:existing_subscription_guid) { UUID.generate(:compact) }
|
|
let(:reshare_entity) { Fabricate(:reshare_entity, author: archive_author) }
|
|
let(:reshare_entity_with_no_root) {
|
|
Fabricate(:reshare_entity, author: archive_author, root_guid: nil, root_author: nil)
|
|
}
|
|
let(:unknown_status_message_entity) { Fabricate(:status_message_entity, author: archive_author, public: false) }
|
|
let(:known_status_message_entity) { Fabricate(:status_message_entity, author: archive_author, public: false) }
|
|
let(:colliding_status_message_entity) { Fabricate(:status_message_entity, author: archive_author) }
|
|
let(:status_message_with_poll_entity) {
|
|
Fabricate(:status_message_entity,
|
|
author: archive_author,
|
|
poll: Fabricate(:poll_entity))
|
|
}
|
|
let(:status_message_with_location_entity) {
|
|
Fabricate(:status_message_entity,
|
|
author: archive_author,
|
|
location: Fabricate(:location_entity))
|
|
}
|
|
let(:status_message_with_photos_entity) {
|
|
Fabricate(:status_message_entity,
|
|
author: archive_author,
|
|
photos: [
|
|
Fabricate(:photo_entity, author: archive_author),
|
|
Fabricate(:photo_entity, author: archive_author)
|
|
])
|
|
}
|
|
let(:comment_entity) {
|
|
Fabricate(:comment_entity, author: archive_author, author_signature: "ignored XXXXXXXXXXXXXXXXXXXXXXXXXXX")
|
|
}
|
|
let(:like_entity) {
|
|
Fabricate(:like_entity,
|
|
author: archive_author,
|
|
author_signature: "ignored XXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
|
parent_guid: FactoryBot.create(:status_message).guid)
|
|
}
|
|
let(:poll_participation_entity) {
|
|
poll = FactoryBot.create(:status_message_with_poll).poll
|
|
Fabricate(:poll_participation_entity,
|
|
author: archive_author,
|
|
author_signature: "ignored XXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
|
poll_answer_guid: poll.poll_answers.first.guid,
|
|
parent_guid: poll.guid)
|
|
}
|
|
let(:unknown_poll_guid) { UUID.generate(:compact) }
|
|
let(:unknown_poll_answer_guid) { UUID.generate(:compact) }
|
|
let(:poll_participation_entity_unknown_root) {
|
|
Fabricate(:poll_participation_entity,
|
|
author: archive_author,
|
|
author_signature: "ignored XXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
|
poll_answer_guid: unknown_poll_answer_guid,
|
|
parent_guid: unknown_poll_guid)
|
|
}
|
|
let(:others_comment_entity) {
|
|
data = Fabricate.attributes_for(:comment_entity,
|
|
author: remote_user_on_pod_b.diaspora_handle,
|
|
parent_guid: unknown_status_message_entity.guid)
|
|
data[:author_signature] = Fabricate(:comment_entity, data).sign_with_key(remote_user_on_pod_b.encryption_key)
|
|
Fabricate(:comment_entity, data)
|
|
}
|
|
|
|
let(:post_subscriber) { FactoryBot.create(:person) }
|
|
let(:known_contact_person) { FactoryBot.create(:person) }
|
|
let!(:collided_status_message) { FactoryBot.create(:status_message, guid: colliding_status_message_entity.guid) }
|
|
let!(:collided_like) { FactoryBot.create(:like, guid: like_entity.guid) }
|
|
let!(:reshare_root_author) { FactoryBot.create(:person, diaspora_handle: reshare_entity.root_author) }
|
|
|
|
# This is for testing migrated contacts handling
|
|
let(:account_migration) { FactoryBot.create(:account_migration).tap(&:perform!) }
|
|
let(:migrated_contact_diaspora_id) { account_migration.old_person.diaspora_handle }
|
|
let(:migrated_contact_new_diaspora_id) { account_migration.new_person.diaspora_handle }
|
|
|
|
let(:posts_in_archive) {
|
|
[
|
|
reshare_entity,
|
|
unknown_status_message_entity,
|
|
known_status_message_entity,
|
|
reshare_entity_with_no_root,
|
|
colliding_status_message_entity,
|
|
status_message_with_poll_entity,
|
|
status_message_with_location_entity,
|
|
status_message_with_photos_entity
|
|
]
|
|
}
|
|
|
|
let(:posts_in_archive_json) {
|
|
posts = posts_in_archive.map {|post|
|
|
post.to_json.as_json
|
|
}
|
|
posts[0]["subscribed_pods_uris"] = []
|
|
posts[1]["subscribed_users_ids"] = [post_subscriber.diaspora_handle]
|
|
posts[2]["subscribed_users_ids"] = [post_subscriber.diaspora_handle]
|
|
posts[3]["subscribed_pods_uris"] = []
|
|
posts[4]["subscribed_pods_uris"] = []
|
|
posts[5]["subscribed_pods_uris"] = []
|
|
posts[6]["subscribed_pods_uris"] = []
|
|
posts[7]["subscribed_pods_uris"] = []
|
|
posts.to_json
|
|
}
|
|
|
|
let(:archive_json) { <<~JSON }
|
|
{
|
|
"user": {
|
|
"username": "previous_username",
|
|
"email": "mail@example.com",
|
|
"private_key": #{archive_private_key.export.dump},
|
|
"profile": {
|
|
"entity_type": "profile",
|
|
"entity_data": {
|
|
"author": "#{archive_author}"
|
|
}
|
|
},
|
|
"contacts": [
|
|
{
|
|
"sharing": true,
|
|
"receiving": false,
|
|
"following": true,
|
|
"followed": false,
|
|
"account_id": "#{contact1_diaspora_id}",
|
|
"contact_groups_membership": []
|
|
},
|
|
{
|
|
"sharing": true,
|
|
"receiving": true,
|
|
"following": true,
|
|
"followed": true,
|
|
"account_id": "#{migrated_contact_diaspora_id}",
|
|
"contact_groups_membership": ["Family"]
|
|
},
|
|
{
|
|
"sharing": true,
|
|
"receiving": true,
|
|
"following": true,
|
|
"followed": true,
|
|
"account_id": "#{contact2_diaspora_id}",
|
|
"contact_groups_membership": ["Family"]
|
|
}
|
|
],
|
|
"contact_groups": [
|
|
{"name":"Friends"}
|
|
],
|
|
"post_subscriptions": [
|
|
"#{unknown_subscription_guid}",
|
|
"#{existing_subscription_guid}"
|
|
],
|
|
"posts": #{posts_in_archive_json},
|
|
"relayables": [
|
|
#{comment_entity.to_json.as_json.to_json},
|
|
#{like_entity.to_json.as_json.to_json},
|
|
#{poll_participation_entity.to_json.as_json.to_json},
|
|
#{poll_participation_entity_unknown_root.to_json.as_json.to_json}
|
|
]
|
|
},
|
|
"others_data": {
|
|
"relayables": [
|
|
#{others_comment_entity.to_json.as_json.to_json}
|
|
]
|
|
},
|
|
"version": "2.0"
|
|
}
|
|
JSON
|
|
|
|
def expect_reshare_root_fetch(root_author, root_guid)
|
|
expect(DiasporaFederation::Federation::Fetcher)
|
|
.to receive(:fetch_public)
|
|
.with(root_author.diaspora_handle, "Post", root_guid) {
|
|
FactoryBot.create(:status_message, guid: root_guid, author: root_author, public: true)
|
|
}
|
|
end
|
|
|
|
def expect_relayable_parent_fetch(relayable_author, parent_guid, parent_type="Post", &block)
|
|
expect(DiasporaFederation::Federation::Fetcher)
|
|
.to receive(:fetch_public)
|
|
.with(relayable_author, parent_type, parent_guid, &block)
|
|
end
|
|
|
|
let(:new_username) { "newuser" }
|
|
let(:new_user_handle) { "#{new_username}@#{AppConfig.bare_pod_uri}" }
|
|
|
|
let(:archive_file) { Tempfile.new(%w[archive .json]) }
|
|
|
|
def setup_validation_time_expectations
|
|
expect_person_fetch(contact2_diaspora_id, nil)
|
|
|
|
# This is expected to be called during relayable validation
|
|
expect_relayable_parent_fetch(archive_author, comment_entity.parent_guid) {
|
|
FactoryBot.create(:status_message, guid: comment_entity.parent_guid)
|
|
}
|
|
|
|
expect_relayable_parent_fetch(archive_author, unknown_poll_guid, "Poll") {
|
|
FactoryBot.create(
|
|
:poll_answer,
|
|
poll: FactoryBot.create(:poll, guid: unknown_poll_guid),
|
|
guid: unknown_poll_answer_guid
|
|
)
|
|
}
|
|
end
|
|
|
|
before do
|
|
archive_file.write(archive_json)
|
|
archive_file.close
|
|
allow_callbacks(
|
|
%i[queue_public_receive fetch_related_entity fetch_person_url_to fetch_public_key receive_entity
|
|
fetch_private_key]
|
|
)
|
|
end
|
|
|
|
shared_examples "imports archive" do
|
|
it "imports archive" do
|
|
expect_relayable_parent_fetch(archive_author, unknown_subscription_guid) {
|
|
FactoryBot.create(:status_message, guid: unknown_subscription_guid)
|
|
}
|
|
|
|
expect_reshare_root_fetch(reshare_root_author, reshare_entity.root_guid)
|
|
|
|
service = MigrationService.new(archive_file.path, new_username)
|
|
service.validate
|
|
expect(service.warnings).to eq(
|
|
["reshare Reshare:#{reshare_entity_with_no_root.guid} doesn't have a root, ignored"]
|
|
)
|
|
service.perform!
|
|
user = User.find_by(username: new_username)
|
|
expect(user).not_to be_nil
|
|
|
|
unless Person.by_account_identifier(archive_author).nil?
|
|
expect(AccountMigration.where(new_person: user.person).any?).to be_truthy
|
|
|
|
existing_contact.reload
|
|
expect(existing_contact.person).to eq(user.person)
|
|
expect(existing_contact.sharing).to be_truthy
|
|
expect(existing_contact.receiving).to be_truthy
|
|
end
|
|
|
|
status_message = StatusMessage.find_by(guid: unknown_status_message_entity.guid)
|
|
expect(status_message.author).to eq(user.person)
|
|
# TODO: rewrite this expectation when new subscription implementation is there
|
|
# expect(status_message.participants).to include(post_subscriber)
|
|
|
|
status_message = StatusMessage.find_by(guid: known_status_message_entity.guid)
|
|
expect(status_message.author).to eq(user.person)
|
|
# TODO: rewrite this expectation when new subscription implementation is there
|
|
# expect(status_message.participants).to include(post_subscriber)
|
|
|
|
status_message = StatusMessage.find_by(guid: status_message_with_poll_entity.guid)
|
|
expect(status_message.author).to eq(user.person)
|
|
poll = status_message.poll
|
|
expect(poll).not_to be_nil
|
|
expect(poll.guid).to eq(status_message_with_poll_entity.poll.guid)
|
|
expect(poll.question).to eq(status_message_with_poll_entity.poll.question)
|
|
expect(poll.poll_answers.pluck(:answer, :guid)).to eq(
|
|
status_message_with_poll_entity.poll.poll_answers.map {|answer| [answer.answer, answer.guid] }
|
|
)
|
|
|
|
status_message = StatusMessage.find_by(guid: status_message_with_location_entity.guid)
|
|
expect(status_message.author).to eq(user.person)
|
|
expect(status_message.location.address).to eq(status_message_with_location_entity.location.address)
|
|
expect(status_message.location.lat).to eq(status_message_with_location_entity.location.lat)
|
|
expect(status_message.location.lng).to eq(status_message_with_location_entity.location.lng)
|
|
|
|
status_message = StatusMessage.find_by(guid: status_message_with_photos_entity.guid)
|
|
expect(status_message.author).to eq(user.person)
|
|
expect(
|
|
status_message.photos.pluck(:guid, :text, :remote_photo_path, :remote_photo_name, :width, :height)
|
|
).to match_array(
|
|
status_message_with_photos_entity.photos.map {|photo|
|
|
[photo.guid, photo.text, photo.remote_photo_path, photo.remote_photo_name, photo.width, photo.height]
|
|
}
|
|
)
|
|
|
|
comment = Comment.find_by(guid: comment_entity.guid)
|
|
expect(comment.author).to eq(user.person)
|
|
|
|
# Here we're testing the case when the like in the archive has the guid colliding with another known like
|
|
like = Like.find_by(guid: like_entity.guid)
|
|
expect(like.author).not_to eq(user.person)
|
|
|
|
contact = user.contacts.find_by(person: Person.by_account_identifier(contact1_diaspora_id))
|
|
expect(contact).to be_nil
|
|
|
|
contact = user.contacts.find_by(person: Person.by_account_identifier(contact2_diaspora_id))
|
|
expect(contact).not_to be_nil
|
|
expect(contact.sharing).to be_falsey
|
|
expect(contact.receiving).to be_truthy
|
|
|
|
contact = user.contacts.find_by(person: Person.by_account_identifier(migrated_contact_new_diaspora_id))
|
|
expect(contact).not_to be_nil
|
|
expect(contact.sharing).to be_falsey
|
|
expect(contact.receiving).to be_truthy
|
|
|
|
aspect = user.aspects.find_by(name: "Friends")
|
|
expect(aspect).not_to be_nil
|
|
|
|
poll_participation = PollParticipation.find_by(author: user.person, guid: poll_participation_entity.guid)
|
|
expect(poll_participation).not_to be_nil
|
|
expect(poll_participation.parent.guid).to eq(poll_participation_entity.parent_guid)
|
|
expect(poll_participation.poll_answer.guid).to eq(poll_participation_entity.poll_answer_guid)
|
|
|
|
comment = Comment.find_by(guid: others_comment_entity.guid)
|
|
expect(comment.author.diaspora_handle).to eq(others_comment_entity.author)
|
|
expect(comment.parent.author.diaspora_handle).to eq(user.diaspora_handle)
|
|
end
|
|
end
|
|
|
|
context "photo migration" do
|
|
it "doesn't include a new remote_photo_path" do
|
|
service = MigrationService.new(archive_file.path, new_username)
|
|
service.send(:find_or_create_user)
|
|
account_migration = service.send(:account_migration)
|
|
expect(account_migration.remote_photo_path).to be_nil
|
|
end
|
|
|
|
it "includes url to new pod image upload folder in remote_photo_path" do
|
|
service = MigrationService.new(archive_file.path, new_username, photo_migration: true)
|
|
service.send(:find_or_create_user)
|
|
account_migration = service.send(:account_migration)
|
|
expect(account_migration.remote_photo_path).to eq("#{AppConfig.pod_uri}uploads/images/")
|
|
end
|
|
|
|
it "includes url to S3 image upload folder in remote_photo_path when S3 is enabled" do
|
|
AppConfig.environment.s3.enable = true
|
|
AppConfig.environment.s3.bucket = "test-bucket"
|
|
|
|
service = MigrationService.new(archive_file.path, new_username, photo_migration: true)
|
|
service.send(:find_or_create_user)
|
|
account_migration = service.send(:account_migration)
|
|
expect(account_migration.remote_photo_path).to eq("https://test-bucket.s3.amazonaws.com/uploads/images/")
|
|
end
|
|
end
|
|
|
|
context "compressed archives" do
|
|
it "uncompresses gz archive" do
|
|
gz_compressed_file = create_gz_archive
|
|
service = MigrationService.new(gz_compressed_file, new_username)
|
|
uncompressed_file = service.send(:archive_file)
|
|
json = uncompressed_file.read
|
|
expect {
|
|
JSON.parse(json)
|
|
}.not_to raise_error
|
|
end
|
|
|
|
it "uncompresses zip archive" do
|
|
zip_compressed_file = create_zip_archive
|
|
service = MigrationService.new(zip_compressed_file, new_username)
|
|
uncompressed_file = service.send(:archive_file)
|
|
json = uncompressed_file.read
|
|
expect {
|
|
JSON.parse(json)
|
|
}.not_to raise_error
|
|
end
|
|
|
|
def create_gz_archive
|
|
target_file = Tempfile.new(%w[archive .json.gz]).path
|
|
Zlib::GzipWriter.open(target_file) do |gz|
|
|
File.open(archive_file.path).each do |line|
|
|
gz.write line
|
|
end
|
|
end
|
|
target_file
|
|
end
|
|
|
|
def create_zip_archive
|
|
target_file = Tempfile.new(%w[archive .zip]).path
|
|
Zip::OutputStream.open(target_file) do |zip|
|
|
zip.put_next_entry("archive.json")
|
|
File.open(archive_file.path).each do |line|
|
|
zip.write line
|
|
end
|
|
end
|
|
target_file
|
|
end
|
|
end
|
|
|
|
context "old user is a known remote user" do
|
|
let(:old_person) {
|
|
FactoryBot.create(:person,
|
|
profile: FactoryBot.build(:profile),
|
|
serialized_public_key: archive_private_key.public_key.export,
|
|
diaspora_handle: archive_author)
|
|
}
|
|
|
|
# Some existing data for old_person to test data merge/migration
|
|
let!(:existing_contact) { FactoryBot.create(:contact, person: old_person, sharing: true, receiving: true) }
|
|
|
|
let!(:existing_subscription) {
|
|
FactoryBot.create(:participation,
|
|
author: old_person,
|
|
target: FactoryBot.create(:status_message, guid: existing_subscription_guid))
|
|
}
|
|
let!(:existing_status_message) {
|
|
FactoryBot.create(:status_message,
|
|
author: old_person,
|
|
guid: known_status_message_entity.guid).tap {|status_message|
|
|
status_message.participants << post_subscriber
|
|
}
|
|
}
|
|
|
|
it_behaves_like "imports archive" do
|
|
before do
|
|
setup_validation_time_expectations
|
|
end
|
|
end
|
|
|
|
context "when account migration already exists" do
|
|
before do
|
|
setup_validation_time_expectations
|
|
FactoryBot.create(:account_migration, old_person: old_person)
|
|
end
|
|
|
|
it "raises exception" do
|
|
expect {
|
|
MigrationService.new(archive_file.path, new_username).validate
|
|
}.to raise_error(MigrationService::MigrationAlreadyExists)
|
|
end
|
|
end
|
|
|
|
describe "#only_import?" do
|
|
it "returns false" do
|
|
service = MigrationService.new(archive_file.path, new_username)
|
|
expect(service.only_import?).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
context "old user is unknown" do
|
|
context "and non-fetchable" do
|
|
before do
|
|
expect(DiasporaFederation::Discovery::Discovery).to receive(:new).with(archive_author).and_call_original
|
|
stub_request(:get, "https://#{old_pod_hostname}/.well-known/webfinger?resource=acct:#{archive_author}")
|
|
.to_return(status: 404)
|
|
|
|
expect_relayable_parent_fetch(archive_author, existing_subscription_guid)
|
|
.and_raise(DiasporaFederation::Federation::Fetcher::NotFetchable)
|
|
|
|
setup_validation_time_expectations
|
|
end
|
|
|
|
include_examples "imports archive"
|
|
end
|
|
|
|
describe "#only_import?" do
|
|
it "returns true" do
|
|
service = MigrationService.new(archive_file.path, new_username)
|
|
expect(service.only_import?).to be_truthy
|
|
end
|
|
end
|
|
end
|
|
end
|