Merge pull request #6750 from cmrd-senya/account_migration_message
Account migration model/message
This commit is contained in:
commit
cbc3900d59
28 changed files with 1073 additions and 76 deletions
|
|
@ -56,6 +56,7 @@ If so, please delete it since it will prevent the federation from working proper
|
|||
* Support cmd+enter to submit posts, comments and conversations [#7524](https://github.com/diaspora/diaspora/pull/7524)
|
||||
* Add markdown editor for posts, comments and conversations on mobile [#7235](https://github.com/diaspora/diaspora/pull/7235)
|
||||
* Mark as "Mobile Web App Capable" on Android [#7534](https://github.com/diaspora/diaspora/pull/7534)
|
||||
* Add support for receiving account migrations [#6750](https://github.com/diaspora/diaspora/pull/6750)
|
||||
|
||||
# 0.6.8.0
|
||||
|
||||
|
|
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
describe StreamsController, :type => :controller do
|
||||
describe '#multi' do
|
||||
before do
|
||||
allow(Workers::SendPublic).to receive(:perform_async)
|
||||
sign_in alice, scope: :user
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -14,6 +14,7 @@ describe DataGenerator do
|
|||
generator.generic_user_data
|
||||
expect(user.aspects).not_to be_empty
|
||||
expect(Post.subscribed_by(user)).not_to be_empty
|
||||
expect(Contact.where(user: user).mutual).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -106,8 +106,6 @@ RSpec.configure do |config|
|
|||
I18n.locale = :en
|
||||
stub_request(:post, "https://pubsubhubbub.appspot.com/")
|
||||
$process_queue = false
|
||||
allow(Workers::SendPublic).to receive(:perform_async)
|
||||
allow(Workers::SendPrivate).to receive(:perform_async)
|
||||
end
|
||||
|
||||
config.expect_with :rspec do |expect_config|
|
||||
|
|
|
|||
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
|
||||
|
|
@ -38,6 +38,7 @@ class DataGenerator
|
|||
private_post_as_receipient
|
||||
tag_following
|
||||
generic_person_data
|
||||
remote_mutual_friend
|
||||
end
|
||||
|
||||
def generic_person_data
|
||||
|
|
@ -98,6 +99,10 @@ class DataGenerator
|
|||
}
|
||||
end
|
||||
|
||||
def remote_mutual_friend
|
||||
FactoryGirl.create(:contact, user: user, sharing: true, receiving: true)
|
||||
end
|
||||
|
||||
def first_aspect
|
||||
user.aspects.first || FactoryGirl.create(:aspect, user: user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ class User
|
|||
alias_method :share_with_original, :share_with
|
||||
|
||||
def share_with(*args)
|
||||
disable_send_workers
|
||||
|
||||
inlined_jobs do
|
||||
share_with_original(*args)
|
||||
end
|
||||
|
|
@ -13,6 +15,8 @@ class User
|
|||
end
|
||||
|
||||
def post(class_name, opts = {})
|
||||
disable_send_workers
|
||||
|
||||
inlined_jobs do
|
||||
aspects = self.aspects_from_ids(opts[:to])
|
||||
|
||||
|
|
@ -22,11 +26,9 @@ class User
|
|||
self.aspects.reload
|
||||
|
||||
dispatch_opts = {
|
||||
url: Rails.application.routes.url_helpers.post_url(
|
||||
p,
|
||||
host: AppConfig.pod_uri.to_s
|
||||
),
|
||||
to: opts[:to]}
|
||||
url: Rails.application.routes.url_helpers.post_url(p, host: AppConfig.pod_uri.to_s),
|
||||
to: opts[:to]
|
||||
}
|
||||
dispatch_post(p, dispatch_opts)
|
||||
end
|
||||
unless opts[:created_at]
|
||||
|
|
@ -40,4 +42,11 @@ class User
|
|||
def build_comment(options={})
|
||||
Comment::Generator.new(self, options.delete(:post), options.delete(:text)).build(options)
|
||||
end
|
||||
|
||||
def disable_send_workers
|
||||
RSpec.current_example&.example_group_instance&.instance_eval do
|
||||
allow(Workers::SendPrivate).to receive(:perform_async)
|
||||
allow(Workers::SendPublic).to receive(:perform_async)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue