Account migration model and message support
This commit introduces support for AccountMigration federation message receive. It covers the cases when the new home pod for a user is remote respective to the recepient pod of the message. It also allows to initiate migration locally by a podmin from the rails console. This will give the pods a possibility to understand the account migration event on the federation level and thus future version which will implement migration will be backward compatible with the pods starting from this commit.
This commit is contained in:
parent
e2979df65a
commit
45619cb153
22 changed files with 1051 additions and 69 deletions
165
app/models/account_migration.rb
Normal file
165
app/models/account_migration.rb
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
class AccountMigration < ApplicationRecord
|
||||
include Diaspora::Federated::Base
|
||||
|
||||
belongs_to :old_person, class_name: "Person"
|
||||
belongs_to :new_person, class_name: "Person"
|
||||
|
||||
validates :old_person, uniqueness: true
|
||||
validates :new_person, uniqueness: true
|
||||
|
||||
after_create :lock_old_user!
|
||||
|
||||
attr_accessor :old_private_key
|
||||
|
||||
def receive(*)
|
||||
perform!
|
||||
end
|
||||
|
||||
def public?
|
||||
true
|
||||
end
|
||||
|
||||
def sender
|
||||
@sender ||= old_user || ephemeral_sender
|
||||
end
|
||||
|
||||
# executes a migration plan according to this AccountMigration object
|
||||
def perform!
|
||||
raise "already performed" if performed?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
account_deleter.tombstone_person_and_profile
|
||||
account_deleter.close_user if user_left_our_pod?
|
||||
account_deleter.tombstone_user if user_changed_id_locally?
|
||||
|
||||
update_all_references
|
||||
end
|
||||
|
||||
dispatch if locally_initiated?
|
||||
dispatch_contacts if remotely_initiated?
|
||||
end
|
||||
|
||||
def performed?
|
||||
old_person.closed_account?
|
||||
end
|
||||
|
||||
# We assume that migration message subscribers are people that are subscribed to a new user profile updates.
|
||||
# Since during the migration we update contact references, this includes all the contacts of the old person.
|
||||
# In case when a user migrated to our pod from a remote one, we include remote person to subscribers so that
|
||||
# the new pod is informed about the migration as well.
|
||||
def subscribers
|
||||
new_user.profile.subscribers.remote.to_a.tap do |subscribers|
|
||||
subscribers.push(old_person) if old_person.remote?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Normally pod initiates migration locally when the new user is local. Then the pod creates AccountMigration object
|
||||
# itself. If new user is remote, then AccountMigration object is normally received via the federation and this is
|
||||
# remote initiation then.
|
||||
def remotely_initiated?
|
||||
new_person.remote?
|
||||
end
|
||||
|
||||
def locally_initiated?
|
||||
!remotely_initiated?
|
||||
end
|
||||
|
||||
def old_user
|
||||
old_person.owner
|
||||
end
|
||||
|
||||
def new_user
|
||||
new_person.owner
|
||||
end
|
||||
|
||||
def lock_old_user!
|
||||
old_user&.lock_access!
|
||||
end
|
||||
|
||||
def user_left_our_pod?
|
||||
old_user && !new_user
|
||||
end
|
||||
|
||||
def user_changed_id_locally?
|
||||
old_user && new_user
|
||||
end
|
||||
|
||||
# We need to resend contacts of users of our pod for the remote new person so that the remote pod received this
|
||||
# contact information from the authoritative source.
|
||||
def dispatch_contacts
|
||||
new_person.contacts.sharing.each do |contact|
|
||||
Diaspora::Federation::Dispatcher.defer_dispatch(contact.user, contact)
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch
|
||||
Diaspora::Federation::Dispatcher.build(sender, self).dispatch
|
||||
end
|
||||
|
||||
EphemeralUser = Struct.new(:diaspora_handle, :serialized_private_key) do
|
||||
def id
|
||||
diaspora_handle
|
||||
end
|
||||
|
||||
def encryption_key
|
||||
OpenSSL::PKey::RSA.new(serialized_private_key)
|
||||
end
|
||||
end
|
||||
|
||||
def ephemeral_sender
|
||||
raise "can't build sender without old private key defined" if old_private_key.nil?
|
||||
EphemeralUser.new(old_person.diaspora_handle, old_private_key)
|
||||
end
|
||||
|
||||
def update_all_references
|
||||
update_person_references
|
||||
update_user_references if user_changed_id_locally?
|
||||
end
|
||||
|
||||
def person_references
|
||||
references = Person.reflections.reject {|key, _|
|
||||
%w[profile owner notifications pod].include?(key)
|
||||
}
|
||||
|
||||
references.map {|key, value|
|
||||
{value.foreign_key => key}
|
||||
}
|
||||
end
|
||||
|
||||
def user_references
|
||||
references = User.reflections.reject {|key, _|
|
||||
%w[
|
||||
person profile auto_follow_back_aspect invited_by aspect_memberships contact_people followed_tags
|
||||
ignored_people conversation_visibilities pairwise_pseudonymous_identifiers conversations o_auth_applications
|
||||
].include?(key)
|
||||
}
|
||||
|
||||
references.map {|key, value|
|
||||
{value.foreign_key => key}
|
||||
}
|
||||
end
|
||||
|
||||
def update_person_references
|
||||
logger.debug "Updating references from person id=#{old_person.id} to person id=#{new_person.id}"
|
||||
update_references(person_references, old_person, new_person.id)
|
||||
end
|
||||
|
||||
def update_user_references
|
||||
logger.debug "Updating references from user id=#{old_user.id} to user id=#{new_user.id}"
|
||||
update_references(user_references, old_user, new_user.id)
|
||||
end
|
||||
|
||||
def update_references(references, object, new_id)
|
||||
references.each do |pair|
|
||||
key_id = pair.flatten[0]
|
||||
association = pair.flatten[1]
|
||||
object.send(association).update_all(key_id => new_id)
|
||||
end
|
||||
end
|
||||
|
||||
def account_deleter
|
||||
@account_deleter ||= AccountDeleter.new(old_person)
|
||||
end
|
||||
end
|
||||
|
|
@ -40,7 +40,10 @@ class Person < ApplicationRecord
|
|||
has_many :likes, foreign_key: :author_id, dependent: :destroy # This person's own likes
|
||||
has_many :participations, :foreign_key => :author_id, :dependent => :destroy
|
||||
has_many :poll_participations, foreign_key: :author_id, dependent: :destroy
|
||||
has_many :conversation_visibilities
|
||||
has_many :conversation_visibilities, dependent: :destroy
|
||||
has_many :messages, foreign_key: :author_id, dependent: :destroy
|
||||
has_many :conversations, foreign_key: :author_id, dependent: :destroy
|
||||
has_many :blocks, dependent: :destroy
|
||||
|
||||
has_many :roles
|
||||
|
||||
|
|
@ -307,11 +310,6 @@ class Person < ApplicationRecord
|
|||
serialized_public_key
|
||||
end
|
||||
|
||||
def exported_key= new_key
|
||||
raise "Don't change a key" if serialized_public_key
|
||||
serialized_public_key = new_key
|
||||
end
|
||||
|
||||
# discovery (webfinger)
|
||||
def self.find_or_fetch_by_identifier(diaspora_id)
|
||||
# exiting person?
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ class Profile < ApplicationRecord
|
|||
end
|
||||
|
||||
def tombstone!
|
||||
@tag_string = nil
|
||||
self.taggings.delete_all
|
||||
clearable_fields.each do |field|
|
||||
self[field] = nil
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ class User < ApplicationRecord
|
|||
belongs_to :auto_follow_back_aspect, class_name: "Aspect", optional: true
|
||||
belongs_to :invited_by, class_name: "User", optional: true
|
||||
|
||||
has_many :invited_users, class_name: "User", inverse_of: :invited_by, foreign_key: :invited_by_id
|
||||
|
||||
has_many :aspect_memberships, :through => :aspects
|
||||
|
||||
has_many :contacts
|
||||
|
|
|
|||
14
db/migrate/20170730154117_create_account_migrations.rb
Normal file
14
db/migrate/20170730154117_create_account_migrations.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
class CreateAccountMigrations < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :account_migrations do |t|
|
||||
t.integer :old_person_id, null: false
|
||||
t.integer :new_person_id, null: false
|
||||
end
|
||||
|
||||
add_foreign_key :account_migrations, :people, column: :old_person_id
|
||||
add_foreign_key :account_migrations, :people, column: :new_person_id
|
||||
|
||||
add_index :account_migrations, %i[old_person_id new_person_id], unique: true
|
||||
add_index :account_migrations, :old_person_id, unique: true
|
||||
end
|
||||
end
|
||||
|
|
@ -30,18 +30,20 @@ class AccountDeleter
|
|||
delete_contacts_of_me
|
||||
tombstone_person_and_profile
|
||||
|
||||
if self.user
|
||||
#user deletion methods
|
||||
remove_share_visibilities_on_contacts_posts
|
||||
delete_standard_user_associations
|
||||
disconnect_contacts
|
||||
tombstone_user
|
||||
end
|
||||
close_user if user
|
||||
|
||||
mark_account_deletion_complete
|
||||
end
|
||||
end
|
||||
|
||||
# user deletion methods
|
||||
def close_user
|
||||
remove_share_visibilities_on_contacts_posts
|
||||
disconnect_contacts
|
||||
delete_standard_user_associations
|
||||
tombstone_user
|
||||
end
|
||||
|
||||
#user deletions
|
||||
def normal_ar_user_associates_to_delete
|
||||
%i[tag_followings services aspects user_preferences
|
||||
|
|
@ -53,7 +55,7 @@ class AccountDeleter
|
|||
end
|
||||
|
||||
def ignored_ar_user_associations
|
||||
%i[followed_tags invited_by contact_people aspect_memberships
|
||||
%i[followed_tags invited_by invited_users contact_people aspect_memberships
|
||||
ignored_people share_visibilities conversation_visibilities conversations reports]
|
||||
end
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ class AccountDeleter
|
|||
end
|
||||
|
||||
def disconnect_contacts
|
||||
user.contacts.reload.destroy_all
|
||||
user.contacts.destroy_all
|
||||
end
|
||||
|
||||
# Currently this would get deleted due to the db foreign key constrainsts,
|
||||
|
|
@ -97,12 +99,12 @@ class AccountDeleter
|
|||
end
|
||||
|
||||
def normal_ar_person_associates_to_delete
|
||||
%i[posts photos mentions participations roles]
|
||||
%i[posts photos mentions participations roles blocks]
|
||||
end
|
||||
|
||||
def ignored_or_special_ar_person_associations
|
||||
%i[comments likes poll_participations contacts notification_actors notifications owner profile
|
||||
conversation_visibilities pod]
|
||||
conversation_visibilities pod conversations messages]
|
||||
end
|
||||
|
||||
def mark_account_deletion_complete
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ module Diaspora
|
|||
)
|
||||
end
|
||||
|
||||
def self.account_migration(account_migration)
|
||||
DiasporaFederation::Entities::AccountMigration.new(
|
||||
author: account_migration.sender.diaspora_handle,
|
||||
profile: profile(account_migration.new_person.profile)
|
||||
)
|
||||
end
|
||||
|
||||
def self.comment(comment)
|
||||
DiasporaFederation::Entities::Comment.new(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Diaspora
|
|||
# used in Diaspora::Federation::Receive
|
||||
def self.receiver_for(federation_entity)
|
||||
case federation_entity
|
||||
when DiasporaFederation::Entities::AccountMigration then :account_migration
|
||||
when DiasporaFederation::Entities::Comment then :comment
|
||||
when DiasporaFederation::Entities::Contact then :contact
|
||||
when DiasporaFederation::Entities::Conversation then :conversation
|
||||
|
|
@ -24,6 +25,7 @@ module Diaspora
|
|||
# used in Diaspora::Federation::Entities
|
||||
def self.builder_for(diaspora_entity)
|
||||
case diaspora_entity
|
||||
when AccountMigration then :account_migration
|
||||
when AccountDeletion then :account_deletion
|
||||
when Comment then :comment
|
||||
when Contact then :contact
|
||||
|
|
|
|||
|
|
@ -11,6 +11,14 @@ module Diaspora
|
|||
AccountDeletion.create!(person: author_of(entity))
|
||||
end
|
||||
|
||||
def self.account_migration(entity)
|
||||
profile = profile(entity.profile)
|
||||
AccountMigration.create!(
|
||||
old_person: Person.by_account_identifier(entity.author),
|
||||
new_person: profile.person
|
||||
)
|
||||
end
|
||||
|
||||
def self.comment(entity)
|
||||
receive_relayable(Comment, entity) do
|
||||
Comment.new(
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ FactoryGirl.define do
|
|||
association :person
|
||||
end
|
||||
|
||||
factory :account_migration do
|
||||
association :old_person, factory: :person
|
||||
association :new_person, factory: :person
|
||||
end
|
||||
|
||||
factory :like do
|
||||
association :author, :factory => :person
|
||||
association :target, :factory => :status_message
|
||||
|
|
@ -145,6 +150,11 @@ FactoryGirl.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory(:share_visibility) do
|
||||
user
|
||||
association :shareable, factory: :status_message
|
||||
end
|
||||
|
||||
factory(:location) do
|
||||
sequence(:address) {|n| "Fernsehturm Berlin, #{n}, Berlin, Germany" }
|
||||
sequence(:lat) {|n| 52.520645 + 0.0000001 * n }
|
||||
|
|
@ -222,13 +232,8 @@ FactoryGirl.define do
|
|||
sequence(:uid) { |token| "00000#{token}" }
|
||||
sequence(:access_token) { |token| "12345#{token}" }
|
||||
sequence(:access_secret) { |token| "98765#{token}" }
|
||||
end
|
||||
|
||||
factory :service_user do
|
||||
sequence(:uid) { |id| "a#{id}"}
|
||||
sequence(:name) { |num| "Rob Fergus the #{num.ordinalize}" }
|
||||
association :service
|
||||
photo_url "/assets/user/adams.jpg"
|
||||
user
|
||||
end
|
||||
|
||||
factory :pod do
|
||||
|
|
@ -354,7 +359,18 @@ FactoryGirl.define do
|
|||
text SecureRandom.hex(1000)
|
||||
end
|
||||
|
||||
factory(:status, :parent => :status_message)
|
||||
factory(:status, parent: :status_message)
|
||||
|
||||
factory :block do
|
||||
user
|
||||
person
|
||||
end
|
||||
|
||||
factory :report do
|
||||
user
|
||||
association :item, factory: :status_message
|
||||
text "offensive content"
|
||||
end
|
||||
|
||||
factory :o_auth_application, class: Api::OpenidConnect::OAuthApplication do
|
||||
client_name { "Diaspora Test Client #{r_str}" }
|
||||
|
|
|
|||
|
|
@ -12,25 +12,11 @@ describe "deleteing account", type: :request do
|
|||
DataGenerator.create(subject, :generic_user_data)
|
||||
end
|
||||
|
||||
it "deletes all of the user data" do
|
||||
expect {
|
||||
account_removal_method
|
||||
}.to change(nil, "user preferences empty?") { UserPreference.where(user_id: user.id).empty? }.to(be_truthy)
|
||||
.and(change(nil, "notifications empty?") { Notification.where(recipient_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "blocks empty?") { Block.where(user_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "services empty?") { Service.where(user_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "share visibilities empty?") { ShareVisibility.where(user_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "aspects empty?") { user.aspects.empty? }.to(be_truthy))
|
||||
.and(change(nil, "contacts empty?") { user.contacts.empty? }.to(be_truthy))
|
||||
.and(change(nil, "tag followings empty?") { user.tag_followings.empty? }.to(be_truthy))
|
||||
.and(change(nil, "clearable fields blank?") {
|
||||
user.send(:clearable_fields).map {|field|
|
||||
user.reload[field].blank?
|
||||
}
|
||||
}.to(eq([true] * user.send(:clearable_fields).count)))
|
||||
end
|
||||
it_behaves_like "deletes all of the user data"
|
||||
|
||||
it_behaves_like "it removes the person associations"
|
||||
|
||||
it_behaves_like "it keeps the person conversations"
|
||||
end
|
||||
|
||||
context "of remote person" do
|
||||
|
|
@ -41,5 +27,13 @@ describe "deleteing account", type: :request do
|
|||
end
|
||||
|
||||
it_behaves_like "it removes the person associations"
|
||||
|
||||
it_behaves_like "it keeps the person conversations"
|
||||
|
||||
it_behaves_like "it makes account closed and clears profile" do
|
||||
before do
|
||||
account_removal_method
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
204
spec/integration/account_migration_spec.rb
Normal file
204
spec/integration/account_migration_spec.rb
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
require "integration/federation/federation_helper"
|
||||
|
||||
def create_remote_contact(user, pod_host)
|
||||
FactoryGirl.create(
|
||||
:contact,
|
||||
user: user,
|
||||
person: FactoryGirl.create(
|
||||
:person,
|
||||
pod: Pod.find_or_create_by(url: "http://#{pod_host}"),
|
||||
diaspora_handle: "#{r_str}@#{pod_host}"
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
shared_examples_for "old person account is closed and profile is cleared" do
|
||||
subject { old_user.person }
|
||||
|
||||
before do
|
||||
run_migration
|
||||
subject.reload
|
||||
end
|
||||
|
||||
include_examples "it makes account closed and clears profile"
|
||||
end
|
||||
|
||||
shared_examples_for "old person doesn't have any reference left" do
|
||||
let(:person) { old_user.person }
|
||||
|
||||
before do
|
||||
DataGenerator.create(person, :generic_person_data)
|
||||
end
|
||||
|
||||
def account_removal_method
|
||||
run_migration
|
||||
person.reload
|
||||
end
|
||||
|
||||
include_examples "it removes the person associations"
|
||||
|
||||
include_examples "it removes the person conversations"
|
||||
end
|
||||
|
||||
shared_examples_for "every migration scenario" do
|
||||
it_behaves_like "it updates person references"
|
||||
|
||||
it_behaves_like "old person account is closed and profile is cleared"
|
||||
|
||||
it_behaves_like "old person doesn't have any reference left"
|
||||
end
|
||||
|
||||
shared_examples_for "migration scenarios with local old user" do
|
||||
it "locks the old user account" do
|
||||
run_migration
|
||||
expect(old_user.reload).to be_a_locked_account
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "migration scenarios initiated remotely" do
|
||||
it "resends known contacts to the new user" do
|
||||
contacts = Array.new(2) { FactoryGirl.create(:contact, person: old_user.person, sharing: true) }
|
||||
expect(DiasporaFederation::Federation::Sender).to receive(:private)
|
||||
.twice do |sender_id, obj_str, _urls, _xml|
|
||||
expect(sender_id).to eq(contacts.first.user_id)
|
||||
expect(obj_str).to eq("Contact:#{contacts.first.user.diaspora_handle}:#{new_user.diaspora_handle}")
|
||||
contacts.shift
|
||||
[]
|
||||
end
|
||||
inlined_jobs { run_migration }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "migration scenarios initiated locally" do
|
||||
it "dispatches account migration message to the federation" do
|
||||
expect(DiasporaFederation::Federation::Sender).to receive(:public) do |sender_id, obj_str, urls, xml|
|
||||
if old_user.person.remote?
|
||||
expect(sender_id).to eq(old_user.diaspora_handle)
|
||||
else
|
||||
expect(sender_id).to eq(old_user.id)
|
||||
end
|
||||
expect(obj_str).to eq("AccountMigration:#{old_user.diaspora_handle}:#{new_user.diaspora_handle}")
|
||||
subscribers = [remote_contact.person]
|
||||
subscribers.push(old_user) if old_user.person.remote?
|
||||
expect(urls).to match_array(subscribers.map(&:url).map {|url| "#{url}receive/public" })
|
||||
|
||||
entity = nil
|
||||
expect {
|
||||
magic_env = Nokogiri::XML(xml).root
|
||||
entity = DiasporaFederation::Salmon::MagicEnvelope
|
||||
.unenvelop(magic_env, old_user.diaspora_handle).payload
|
||||
}.not_to raise_error
|
||||
|
||||
expect(entity).to be_a(DiasporaFederation::Entities::AccountMigration)
|
||||
expect(entity.author).to eq(old_user.diaspora_handle)
|
||||
expect(entity.profile.author).to eq(new_user.diaspora_handle)
|
||||
[]
|
||||
end
|
||||
|
||||
inlined_jobs do
|
||||
run_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "account migration" do
|
||||
# this is the case when we receive account migration message from the federation
|
||||
context "remotely initiated" do
|
||||
let(:entity) { create_account_migration_entity(old_user.diaspora_handle, new_user) }
|
||||
|
||||
def run_migration
|
||||
allow_callbacks(%i[queue_public_receive fetch_public_key receive_entity])
|
||||
post_message(generate_payload(entity, old_user))
|
||||
end
|
||||
|
||||
context "both new and old profiles are remote" do
|
||||
include_context "with remote old user"
|
||||
include_context "with remote new user"
|
||||
|
||||
it "creates AccountMigration db object" do
|
||||
run_migration
|
||||
expect(AccountMigration.where(old_person: old_user.person, new_person: new_user.person)).to exist
|
||||
end
|
||||
|
||||
include_examples "every migration scenario"
|
||||
|
||||
include_examples "migration scenarios initiated remotely"
|
||||
end
|
||||
|
||||
# this is the case when we're a pod, which was left by a person in favor of remote one
|
||||
context "old user is local, new user is remote" do
|
||||
include_context "with local old user"
|
||||
include_context "with remote new user"
|
||||
|
||||
include_examples "every migration scenario"
|
||||
|
||||
include_examples "migration scenarios initiated remotely"
|
||||
|
||||
it_behaves_like "migration scenarios with local old user"
|
||||
|
||||
it_behaves_like "deletes all of the user data" do
|
||||
let(:user) { old_user }
|
||||
|
||||
before do
|
||||
DataGenerator.create(user, :generic_user_data)
|
||||
end
|
||||
|
||||
def account_removal_method
|
||||
run_migration
|
||||
user.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "locally initiated" do
|
||||
before do
|
||||
allow(DiasporaFederation.callbacks).to receive(:trigger).and_call_original
|
||||
end
|
||||
|
||||
# this is the case when user migrates to our pod from a remote one
|
||||
context "old user is remote and new user is local" do
|
||||
include_context "with remote old user"
|
||||
include_context "with local new user"
|
||||
|
||||
def run_migration
|
||||
AccountMigration.create!(
|
||||
old_person: old_user.person,
|
||||
new_person: new_user.person,
|
||||
old_private_key: old_user.serialized_private_key
|
||||
).perform!
|
||||
end
|
||||
|
||||
include_examples "every migration scenario"
|
||||
|
||||
it_behaves_like "migration scenarios initiated locally" do
|
||||
let!(:remote_contact) { create_remote_contact(new_user, "remote-friend.org") }
|
||||
end
|
||||
end
|
||||
|
||||
# this is the case when a user changes diaspora id but stays on the same pod
|
||||
context "old user is local and new user is local" do
|
||||
include_context "with local old user"
|
||||
include_context "with local new user"
|
||||
|
||||
def run_migration
|
||||
AccountMigration.create!(old_person: old_user.person, new_person: new_user.person).perform!
|
||||
end
|
||||
|
||||
include_examples "every migration scenario"
|
||||
|
||||
it_behaves_like "migration scenarios initiated locally" do
|
||||
let!(:remote_contact) { create_remote_contact(old_user, "remote-friend.org") }
|
||||
end
|
||||
|
||||
it_behaves_like "migration scenarios with local old user"
|
||||
|
||||
it "clears the old user account" do
|
||||
run_migration
|
||||
expect(old_user.reload).to be_a_clear_account
|
||||
end
|
||||
|
||||
it_behaves_like "it updates user references"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,21 +6,43 @@ def remote_user_on_pod_c
|
|||
@remote_on_c ||= create_remote_user("remote-c.net")
|
||||
end
|
||||
|
||||
def create_remote_user(pod)
|
||||
def allow_private_key_fetch(user)
|
||||
allow(DiasporaFederation.callbacks).to receive(:trigger).with(
|
||||
:fetch_private_key, user.diaspora_handle
|
||||
) { user.encryption_key }
|
||||
end
|
||||
|
||||
def allow_public_key_fetch(user)
|
||||
allow(DiasporaFederation.callbacks).to receive(:trigger).with(
|
||||
:fetch_public_key, user.diaspora_handle
|
||||
) { OpenSSL::PKey::RSA.new(user.person.serialized_public_key) }
|
||||
end
|
||||
|
||||
def create_undiscovered_user(pod)
|
||||
FactoryGirl.build(:user).tap do |user|
|
||||
allow(user).to receive(:person).and_return(
|
||||
FactoryGirl.create(:person,
|
||||
profile: FactoryGirl.build(:profile),
|
||||
serialized_public_key: user.encryption_key.public_key.export,
|
||||
pod: Pod.find_or_create_by(url: "http://#{pod}"),
|
||||
diaspora_handle: "#{user.username}@#{pod}")
|
||||
FactoryGirl.build(:person,
|
||||
profile: FactoryGirl.build(:profile),
|
||||
serialized_public_key: user.encryption_key.public_key.export,
|
||||
pod: Pod.find_or_create_by(url: "http://#{pod}"),
|
||||
diaspora_handle: "#{user.username}@#{pod}")
|
||||
)
|
||||
allow(DiasporaFederation.callbacks).to receive(:trigger).with(
|
||||
:fetch_private_key, user.diaspora_handle
|
||||
) { user.encryption_key }
|
||||
allow(DiasporaFederation.callbacks).to receive(:trigger).with(
|
||||
:fetch_public_key, user.diaspora_handle
|
||||
) { OpenSSL::PKey::RSA.new(user.person.serialized_public_key) }
|
||||
end
|
||||
end
|
||||
|
||||
def expect_person_discovery(undiscovered_user)
|
||||
allow(Person).to receive(:find_or_fetch_by_identifier).with(any_args).and_call_original
|
||||
expect(Person).to receive(:find_or_fetch_by_identifier).with(undiscovered_user.diaspora_handle) {
|
||||
undiscovered_user.person.save!
|
||||
undiscovered_user.person
|
||||
}
|
||||
end
|
||||
|
||||
def create_remote_user(pod)
|
||||
create_undiscovered_user(pod).tap do |user|
|
||||
user.person.save!
|
||||
allow_private_key_fetch(user)
|
||||
allow_public_key_fetch(user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -44,6 +66,14 @@ def create_relayable_entity(entity_name, parent, diaspora_id)
|
|||
)
|
||||
end
|
||||
|
||||
def create_account_migration_entity(diaspora_id, new_user)
|
||||
Fabricate(
|
||||
:account_migration_entity,
|
||||
author: diaspora_id,
|
||||
profile: Diaspora::Federation::Entities.build(new_user.profile)
|
||||
)
|
||||
end
|
||||
|
||||
def generate_payload(entity, remote_user, recipient=nil)
|
||||
magic_env = DiasporaFederation::Salmon::MagicEnvelope.new(
|
||||
entity,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe "Receive federation messages feature" do
|
|||
end
|
||||
|
||||
let(:sender) { remote_user_on_pod_b }
|
||||
let(:sender_id) { remote_user_on_pod_b.diaspora_handle }
|
||||
let(:sender_id) { sender.diaspora_handle }
|
||||
|
||||
context "with public receive" do
|
||||
let(:recipient) { nil }
|
||||
|
|
@ -29,6 +29,80 @@ describe "Receive federation messages feature" do
|
|||
end
|
||||
end
|
||||
|
||||
context "account migration" do
|
||||
# In case when sender is unknown we should just ignore the migration
|
||||
# but this depends on https://github.com/diaspora/diaspora_federation/issues/72
|
||||
# which is low-priority, so we just discover the sender profile in this case.
|
||||
# But there won't be a spec for that.
|
||||
|
||||
let(:entity) { create_account_migration_entity(sender_id, new_user) }
|
||||
|
||||
def run_migration
|
||||
post_message(generate_payload(entity, sender))
|
||||
end
|
||||
|
||||
context "with undiscovered new user profile" do
|
||||
before do
|
||||
allow_callbacks(%i[fetch_public_key])
|
||||
allow_private_key_fetch(new_user)
|
||||
expect_person_discovery(new_user)
|
||||
end
|
||||
|
||||
let(:new_user) { create_undiscovered_user("example.org") }
|
||||
|
||||
it "receives account migration correctly" do
|
||||
run_migration
|
||||
expect(AccountMigration.where(old_person: sender.person, new_person: new_user.person)).to exist
|
||||
expect(AccountMigration.find_by(old_person: sender.person, new_person: new_user.person)).to be_performed
|
||||
end
|
||||
|
||||
it "doesn't accept the same migration for the second time" do
|
||||
run_migration
|
||||
expect {
|
||||
run_migration
|
||||
}.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
|
||||
it "doesn't accept second migration for the same sender" do
|
||||
run_migration
|
||||
expect {
|
||||
entity = create_account_migration_entity(sender_id, create_remote_user("example.org"))
|
||||
post_message(generate_payload(entity, sender))
|
||||
}.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
|
||||
it "doesn't accept second migration for the same new user profile" do
|
||||
run_migration
|
||||
expect {
|
||||
sender = create_remote_user("example.org")
|
||||
entity = create_account_migration_entity(sender.diaspora_handle, new_user)
|
||||
post_message(generate_payload(entity, sender))
|
||||
}.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
|
||||
context "when our pod was left" do
|
||||
let(:sender) { FactoryGirl.create(:user) }
|
||||
|
||||
it "locks the old user account access" do
|
||||
run_migration
|
||||
expect(sender.reload.access_locked?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with discovered profile" do
|
||||
let(:new_user) { create_remote_user("example.org") }
|
||||
|
||||
it "updates person profile with data from entity" do
|
||||
new_user.profile.bio = "my updated biography"
|
||||
expect(entity.profile.bio).to eq("my updated biography")
|
||||
expect(new_user.profile.reload.bio).not_to eq("my updated biography")
|
||||
run_migration
|
||||
expect(new_user.profile.reload.bio).to eq("my updated biography")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "reshare" do
|
||||
it "reshare of public post passes" do
|
||||
post = FactoryGirl.create(:status_message, author: alice.person, public: true)
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@ describe AccountDeleter do
|
|||
end
|
||||
|
||||
describe '#perform' do
|
||||
user_removal_methods = %i[
|
||||
delete_standard_user_associations
|
||||
remove_share_visibilities_on_contacts_posts
|
||||
disconnect_contacts tombstone_user
|
||||
]
|
||||
|
||||
person_removal_methods = %i[
|
||||
delete_contacts_of_me
|
||||
delete_standard_person_associations
|
||||
|
|
@ -32,7 +26,7 @@ describe AccountDeleter do
|
|||
@account_deletion.perform!
|
||||
end
|
||||
|
||||
(user_removal_methods + person_removal_methods).each do |method|
|
||||
[*person_removal_methods, :close_user].each do |method|
|
||||
|
||||
it "calls ##{method.to_s}" do
|
||||
expect(@account_deletion).to receive(method)
|
||||
|
|
@ -64,11 +58,8 @@ describe AccountDeleter do
|
|||
@person_deletion.perform!
|
||||
end
|
||||
|
||||
(user_removal_methods).each do |method|
|
||||
|
||||
it "does not call ##{method.to_s}" do
|
||||
expect(@person_deletion).not_to receive(method)
|
||||
end
|
||||
it "does not call #close_user" do
|
||||
expect(@person_deletion).not_to receive(:close_user)
|
||||
end
|
||||
|
||||
(person_removal_methods).each do |method|
|
||||
|
|
@ -81,6 +72,24 @@ describe AccountDeleter do
|
|||
|
||||
end
|
||||
|
||||
describe "#close_user" do
|
||||
user_removal_methods = %i[
|
||||
delete_standard_user_associations
|
||||
remove_share_visibilities_on_contacts_posts
|
||||
disconnect_contacts tombstone_user
|
||||
]
|
||||
|
||||
after do
|
||||
@account_deletion.perform!
|
||||
end
|
||||
|
||||
user_removal_methods.each do |method|
|
||||
it "calls ##{method}" do
|
||||
expect(@account_deletion).to receive(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#delete_standard_user_associations" do
|
||||
it 'removes all standard user associaltions' do
|
||||
@account_deletion.normal_ar_user_associates_to_delete.each do |asso|
|
||||
|
|
|
|||
|
|
@ -8,6 +8,16 @@ describe Diaspora::Federation::Entities do
|
|||
expect(federation_entity.author).to eq(diaspora_entity.person.diaspora_handle)
|
||||
end
|
||||
|
||||
it "builds an account migration" do
|
||||
diaspora_entity = FactoryGirl.build(:account_migration)
|
||||
diaspora_entity.old_private_key = OpenSSL::PKey::RSA.generate(1024).export
|
||||
federation_entity = described_class.build(diaspora_entity)
|
||||
|
||||
expect(federation_entity).to be_instance_of(DiasporaFederation::Entities::AccountMigration)
|
||||
expect(federation_entity.author).to eq(diaspora_entity.old_person.diaspora_handle)
|
||||
expect(federation_entity.profile.author).to eq(diaspora_entity.new_person.diaspora_handle)
|
||||
end
|
||||
|
||||
it "builds a comment" do
|
||||
diaspora_entity = FactoryGirl.build(:comment)
|
||||
federation_entity = described_class.build(diaspora_entity)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,20 @@ describe Diaspora::Federation::Receive do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".account_migration" do
|
||||
let(:new_person) { FactoryGirl.create(:person) }
|
||||
let(:profile_entity) { Fabricate(:profile_entity, author: new_person.diaspora_handle) }
|
||||
let(:account_migration_entity) {
|
||||
Fabricate(:account_migration_entity, author: sender.diaspora_handle, profile: profile_entity)
|
||||
}
|
||||
|
||||
it "saves the account deletion" do
|
||||
Diaspora::Federation::Receive.account_migration(account_migration_entity)
|
||||
|
||||
expect(AccountMigration.exists?(old_person: sender, new_person: new_person)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe ".comment" do
|
||||
let(:comment_entity) {
|
||||
build_relayable_federation_entity(
|
||||
|
|
|
|||
148
spec/models/account_migration_spec.rb
Normal file
148
spec/models/account_migration_spec.rb
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
require "integration/federation/federation_helper"
|
||||
|
||||
describe AccountMigration, type: :model do
|
||||
describe "create!" do
|
||||
include_context "with local old user"
|
||||
|
||||
it "locks old local user after creation" do
|
||||
expect {
|
||||
AccountMigration.create!(old_person: old_person, new_person: FactoryGirl.create(:person))
|
||||
}.to change { old_user.reload.access_locked? }.to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
let(:old_person) { FactoryGirl.create(:person) }
|
||||
let(:new_person) { FactoryGirl.create(:person) }
|
||||
let(:account_migration) {
|
||||
AccountMigration.create!(old_person: old_person, new_person: new_person)
|
||||
}
|
||||
|
||||
describe "receive" do
|
||||
it "calls perform!" do
|
||||
expect(account_migration).to receive(:perform!)
|
||||
account_migration.receive
|
||||
end
|
||||
end
|
||||
|
||||
describe "sender" do
|
||||
context "with remote old user" do
|
||||
include_context "with remote old user"
|
||||
|
||||
it "creates ephemeral user when private key is provided" do
|
||||
account_migration.old_private_key = old_user.serialized_private_key
|
||||
sender = account_migration.sender
|
||||
expect(sender.id).to eq(old_user.diaspora_handle)
|
||||
expect(sender.diaspora_handle).to eq(old_user.diaspora_handle)
|
||||
expect(sender.encryption_key.to_s).to eq(old_user.encryption_key.to_s)
|
||||
end
|
||||
|
||||
it "raises when no private key is provided" do
|
||||
expect {
|
||||
account_migration.sender
|
||||
}.to raise_error("can't build sender without old private key defined")
|
||||
end
|
||||
end
|
||||
|
||||
context "with local old user" do
|
||||
include_context "with local old user"
|
||||
|
||||
it "matches the old user" do
|
||||
expect(account_migration.sender).to eq(old_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "performed?" do
|
||||
it "is changed after perform!" do
|
||||
expect {
|
||||
account_migration.perform!
|
||||
}.to change(account_migration, :performed?).to be_truthy
|
||||
end
|
||||
|
||||
it "calls old_person.closed_account?" do
|
||||
expect(account_migration.old_person).to receive(:closed_account?)
|
||||
account_migration.performed?
|
||||
end
|
||||
end
|
||||
|
||||
context "with local new user" do
|
||||
include_context "with local new user"
|
||||
|
||||
describe "subscribers" do
|
||||
it "picks remote subscribers of new user profile and old person" do
|
||||
_local_friend, remote_contact = DataGenerator.create(new_user, %i[mutual_friend remote_mutual_friend])
|
||||
expect(account_migration.new_person.owner.profile).to receive(:subscribers).and_call_original
|
||||
expect(account_migration.subscribers).to match_array([remote_contact.person, old_person])
|
||||
end
|
||||
|
||||
context "with local old user" do
|
||||
include_context "with local old user"
|
||||
|
||||
it "doesn't include old person" do
|
||||
expect(account_migration.subscribers).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "perform!" do
|
||||
# TODO: add references update tests
|
||||
# This spec is missing references update tests. We didn't come with a good idea of how to test it
|
||||
# and it is currently covered by integration tests. But it's beter to add these tests at some point
|
||||
# in future when we have more time to think about it.
|
||||
|
||||
let(:embedded_account_deleter) { account_migration.send(:account_deleter) }
|
||||
|
||||
it "raises if already performed" do
|
||||
expect(account_migration).to receive(:performed?).and_return(true)
|
||||
expect {
|
||||
account_migration.perform!
|
||||
}.to raise_error("already performed")
|
||||
end
|
||||
|
||||
it "calls AccountDeleter#tombstone_person_and_profile" do
|
||||
expect(embedded_account_deleter).to receive(:tombstone_person_and_profile)
|
||||
account_migration.perform!
|
||||
end
|
||||
|
||||
context "with local old and remote new users" do
|
||||
include_context "with local old user"
|
||||
|
||||
it "calls AccountDeleter#close_user" do
|
||||
expect(embedded_account_deleter).to receive(:close_user)
|
||||
account_migration.perform!
|
||||
end
|
||||
|
||||
it "resends contacts to the remote pod" do
|
||||
contact = FactoryGirl.create(:contact, person: old_person, sharing: true)
|
||||
expect(Diaspora::Federation::Dispatcher).to receive(:defer_dispatch).with(contact.user, contact)
|
||||
account_migration.perform!
|
||||
end
|
||||
end
|
||||
|
||||
context "with local new and remote old users" do
|
||||
include_context "with remote old user"
|
||||
include_context "with local new user"
|
||||
|
||||
it "dispatches account migration message" do
|
||||
expect(account_migration).to receive(:sender).and_return(old_user)
|
||||
dispatcher = double
|
||||
expect(dispatcher).to receive(:dispatch)
|
||||
expect(Diaspora::Federation::Dispatcher).to receive(:build)
|
||||
.with(old_user, account_migration)
|
||||
.and_return(dispatcher)
|
||||
account_migration.perform!
|
||||
end
|
||||
end
|
||||
|
||||
context "with local old and new users" do
|
||||
include_context "with local old user"
|
||||
include_context "with local new user"
|
||||
|
||||
it "calls AccountDeleter#tombstone_user" do
|
||||
expect(embedded_account_deleter).to receive(:tombstone_user)
|
||||
account_migration.perform!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -287,6 +287,12 @@ describe Profile, :type => :model do
|
|||
expect(@profile.taggings).to receive(:delete_all)
|
||||
@profile.tombstone!
|
||||
end
|
||||
|
||||
it "doesn't recreate taggings if tag string was requested" do
|
||||
@profile.tag_string
|
||||
@profile.tombstone!
|
||||
expect(@profile.taggings).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#clearable_fields" do
|
||||
|
|
|
|||
|
|
@ -2,9 +2,27 @@
|
|||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
shared_examples_for "it removes the person associations" do
|
||||
RSpec::Matchers.define_negated_matcher :remain, :change
|
||||
shared_examples_for "deletes all of the user data" do
|
||||
it "deletes all of the user data" do
|
||||
expect(user).not_to be_a_clear_account
|
||||
|
||||
expect {
|
||||
account_removal_method
|
||||
}.to change(nil, "user preferences empty?") { UserPreference.where(user_id: user.id).empty? }
|
||||
.to(be_truthy)
|
||||
.and(change(nil, "notifications empty?") { Notification.where(recipient_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "blocks empty?") { Block.where(user_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "services empty?") { Service.where(user_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "share visibilities empty?") { ShareVisibility.where(user_id: user.id).empty? }.to(be_truthy))
|
||||
.and(change(nil, "aspects empty?") { user.aspects.empty? }.to(be_truthy))
|
||||
.and(change(nil, "contacts empty?") { user.contacts.empty? }.to(be_truthy))
|
||||
.and(change(nil, "tag followings empty?") { user.tag_followings.empty? }.to(be_truthy))
|
||||
|
||||
expect(user.reload).to be_a_clear_account
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "it removes the person associations" do
|
||||
it "removes all of the person associations" do
|
||||
expect {
|
||||
account_removal_method
|
||||
|
|
@ -20,9 +38,39 @@ shared_examples_for "it removes the person associations" do
|
|||
.and(change(nil, "conversation visibilities empty?") {
|
||||
ConversationVisibility.where(person_id: person.id).empty?
|
||||
}.to(be_truthy))
|
||||
.and(remain(nil, "conversations empty?") { Conversation.where(author: person).empty? }.from(be_falsey))
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "it keeps the person conversations" do
|
||||
RSpec::Matchers.define_negated_matcher :remain, :change
|
||||
|
||||
it "remains the person conversations" do
|
||||
expect {
|
||||
account_removal_method
|
||||
}.to remain(nil, "conversations empty?") { Conversation.where(author: person).empty? }
|
||||
.from(be_falsey)
|
||||
.and(remain(nil, "conversation visibilities of other participants empty?") {
|
||||
ConversationVisibility.where(conversation: Conversation.where(author: person)).empty?
|
||||
}.from(be_falsey))
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "it removes the person conversations" do
|
||||
it "removes the person conversations" do
|
||||
expect {
|
||||
account_removal_method
|
||||
}.to change(nil, "conversations empty?") { Conversation.where(author: person).empty? }
|
||||
.to(be_truthy)
|
||||
.and(change(nil, "conversation visibilities of other participants empty?") {
|
||||
ConversationVisibility.where(conversation: Conversation.where(author: person)).empty?
|
||||
}.to(be_truthy))
|
||||
end
|
||||
end
|
||||
|
||||
# In fact this example group if for testing effect of AccountDeleter.tombstone_person_and_profile
|
||||
shared_examples_for "it makes account closed and clears profile" do
|
||||
it "" do
|
||||
expect(subject).to be_a_closed_account
|
||||
expect(subject.profile).to be_a_clear_profile
|
||||
end
|
||||
end
|
||||
|
|
|
|||
187
spec/shared_behaviors/account_migration.rb
Normal file
187
spec/shared_behaviors/account_migration.rb
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
shared_context "with local old user" do
|
||||
let(:old_user) { FactoryGirl.create(:user) }
|
||||
let(:old_person) { old_user.person }
|
||||
end
|
||||
|
||||
shared_context "with local new user" do
|
||||
let(:new_user) { FactoryGirl.create(:user) }
|
||||
let(:new_person) { new_user.person }
|
||||
end
|
||||
|
||||
shared_context "with remote old user" do
|
||||
let(:old_user) { remote_user_on_pod_c }
|
||||
let(:old_person) { old_user.person }
|
||||
end
|
||||
|
||||
shared_context "with remote new user" do
|
||||
let(:new_user) { remote_user_on_pod_b }
|
||||
let(:new_person) { new_user.person }
|
||||
end
|
||||
|
||||
shared_examples_for "it updates person references" do
|
||||
it "updates contact reference" do
|
||||
contact = FactoryGirl.create(:contact, person: old_person)
|
||||
run_migration
|
||||
expect(contact.reload.person).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates status message reference" do
|
||||
post = FactoryGirl.create(:status_message, author: old_person)
|
||||
run_migration
|
||||
expect(post.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates reshare reference" do
|
||||
reshare = FactoryGirl.create(:reshare, author: old_person)
|
||||
run_migration
|
||||
expect(reshare.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates photo reference" do
|
||||
photo = FactoryGirl.create(:photo, author: old_person)
|
||||
run_migration
|
||||
expect(photo.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates comment reference" do
|
||||
comment = FactoryGirl.create(:comment, author: old_person)
|
||||
run_migration
|
||||
expect(comment.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates like reference" do
|
||||
like = FactoryGirl.create(:like, author: old_person)
|
||||
run_migration
|
||||
expect(like.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates participations reference" do
|
||||
participation = FactoryGirl.create(:participation, author: old_person)
|
||||
run_migration
|
||||
expect(participation.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates poll participations reference" do
|
||||
poll_participation = FactoryGirl.create(:poll_participation, author: old_person)
|
||||
run_migration
|
||||
expect(poll_participation.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates conversation visibilities reference" do
|
||||
conversation = FactoryGirl.build(:conversation)
|
||||
FactoryGirl.create(:contact, user: old_user, person: conversation.author) if old_person.local?
|
||||
conversation.participants << old_person
|
||||
conversation.save!
|
||||
visibility = ConversationVisibility.find_by(person_id: old_person.id)
|
||||
run_migration
|
||||
expect(visibility.reload.person).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates message reference" do
|
||||
message = FactoryGirl.create(:message, author: old_person)
|
||||
run_migration
|
||||
expect(message.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates conversation reference" do
|
||||
conversation = FactoryGirl.create(:conversation, author: old_person)
|
||||
run_migration
|
||||
expect(conversation.reload.author).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates block references" do
|
||||
user = FactoryGirl.create(:user)
|
||||
block = user.blocks.create(person: old_person)
|
||||
run_migration
|
||||
expect(block.reload.person).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates role reference" do
|
||||
role = FactoryGirl.create(:role, person: old_person)
|
||||
run_migration
|
||||
expect(role.reload.person).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates notification actors" do
|
||||
notification = FactoryGirl.build(:notification)
|
||||
notification.actors << old_person
|
||||
notification.save!
|
||||
actor = notification.notification_actors.find_by(person_id: old_person.id)
|
||||
run_migration
|
||||
expect(actor.reload.person).to eq(new_person)
|
||||
end
|
||||
|
||||
it "updates mention reference" do
|
||||
mention = FactoryGirl.create(:mention, person: old_person)
|
||||
run_migration
|
||||
expect(mention.reload.person).to eq(new_person)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "it updates user references" do
|
||||
it "updates invited users reference" do
|
||||
invited_user = FactoryGirl.create(:user, invited_by: old_user)
|
||||
run_migration
|
||||
expect(invited_user.reload.invited_by).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates aspect reference" do
|
||||
aspect = FactoryGirl.create(:aspect, user: old_user, name: r_str)
|
||||
run_migration
|
||||
expect(aspect.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates contact reference" do
|
||||
contact = FactoryGirl.create(:contact, user: old_user)
|
||||
run_migration
|
||||
expect(contact.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates services reference" do
|
||||
service = FactoryGirl.create(:service, user: old_user)
|
||||
run_migration
|
||||
expect(service.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates user preference references" do
|
||||
pref = UserPreference.create!(user: old_user, email_type: "also_commented")
|
||||
run_migration
|
||||
expect(pref.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates tag following references" do
|
||||
tag_following = FactoryGirl.create(:tag_following, user: old_user)
|
||||
run_migration
|
||||
expect(tag_following.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates blocks refrences" do
|
||||
block = FactoryGirl.create(:block, user: old_user)
|
||||
run_migration
|
||||
expect(block.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates notification refrences" do
|
||||
notification = FactoryGirl.create(:notification, recipient: old_user)
|
||||
run_migration
|
||||
expect(notification.reload.recipient).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates report refrences" do
|
||||
report = FactoryGirl.create(:report, user: old_user)
|
||||
run_migration
|
||||
expect(report.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates authorization refrences" do
|
||||
authorization = FactoryGirl.create(:auth_with_read, user: old_user)
|
||||
run_migration
|
||||
expect(authorization.reload.user).to eq(new_user)
|
||||
end
|
||||
|
||||
it "updates share visibility refrences" do
|
||||
share_visibility = FactoryGirl.create(:share_visibility, user: old_user)
|
||||
run_migration
|
||||
expect(share_visibility.reload.user).to eq(new_user)
|
||||
end
|
||||
end
|
||||
43
spec/support/account_matchers.rb
Normal file
43
spec/support/account_matchers.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
RSpec::Matchers.define :be_a_discovered_person do
|
||||
match do |person|
|
||||
!Person.by_account_identifier(person.diaspora_handle).nil?
|
||||
end
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :be_a_closed_account do
|
||||
match(&:closed_account?)
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :be_a_locked_account do
|
||||
match(&:access_locked?)
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :be_a_clear_profile do
|
||||
match do |profile|
|
||||
attributes = %i[
|
||||
diaspora_handle first_name last_name image_url image_url_small image_url_medium birthday gender bio
|
||||
location nsfw public_details
|
||||
].map {|attribute| profile[attribute] }
|
||||
|
||||
profile.taggings.empty? && !profile.searchable && attributes.reject(&:nil?).empty?
|
||||
end
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :be_a_clear_account do
|
||||
match do |user|
|
||||
attributes = %i[
|
||||
language reset_password_token remember_created_at sign_in_count current_sign_in_at last_sign_in_at
|
||||
current_sign_in_ip last_sign_in_ip invited_by_id authentication_token unconfirmed_email confirm_email_token
|
||||
auto_follow_back auto_follow_back_aspect_id reset_password_sent_at last_seen color_theme
|
||||
].map {|attribute| user[attribute] }
|
||||
|
||||
user.disable_mail &&
|
||||
user.strip_exif &&
|
||||
!user.getting_started &&
|
||||
!user.show_community_spotlight_in_stream &&
|
||||
!user.post_default_public &&
|
||||
user.email == "deletedaccount_#{user.id}@example.org" &&
|
||||
user.hidden_shareables.empty? &&
|
||||
attributes.reject(&:nil?).empty?
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue