From 05612ef7337d1dc843949e780259ad3c36ddc4be Mon Sep 17 00:00:00 2001 From: Ilya Zhitomirskiy Date: Wed, 2 Nov 2011 18:49:57 -0700 Subject: [PATCH] ms iz wip --- app/models/account_deletion.rb | 85 ++++++++++++++ app/models/contact.rb | 5 +- app/models/invitation.rb | 11 ++ app/models/user.rb | 1 + spec/factories.rb | 19 ++++ spec/helper_methods.rb | 11 ++ spec/integration/account_deletion_spec.rb | 54 +++++++++ spec/misc_spec.rb | 18 +++ spec/models/account_deletion_spec.rb | 130 ++++++++++++++++++++++ spec/models/contact_spec.rb | 10 ++ spec/models/invitation_spec.rb | 11 ++ spec/models/tag_following_spec.rb | 2 +- 12 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 app/models/account_deletion.rb create mode 100644 spec/integration/account_deletion_spec.rb create mode 100644 spec/models/account_deletion_spec.rb diff --git a/app/models/account_deletion.rb b/app/models/account_deletion.rb new file mode 100644 index 000000000..0b6070bf9 --- /dev/null +++ b/app/models/account_deletion.rb @@ -0,0 +1,85 @@ +# Copyright (c) 2010-2011, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +class AccountDeletion + attr_accessor :person, :user + + def initialize(diaspora_id) + self.person = Person.where(:diaspora_handle => diaspora_id).first + self.user = self.person.owner + end + + def perform! + delete_standard_associations + disassociate_invitations + delete_mentions + delete_contacts_of_me + disconnect_contacts + delete_posts + end + + #user deletions + def normal_ar_user_associates_to_delete + [:tag_followings, :authorizations, :invitations_to_me, :services, :aspects, :user_preferences, :notifications] + end + + def special_ar_user_associations + [:invitations_from_me, :person, :contacts] + end + + def ignored_ar_user_associations + [:followed_tags, :invited_by, :contact_people, :applications, :aspect_memberships] + end + + def delete_standard_associations + normal_ar_user_associates_to_delete.each do |asso| + user.send(asso).delete_all + end + end + + def disassociate_invitations + user.invitations_from_me.each do |inv| + inv.convert_to_admin! + end + end + + def disconnect_contacts + user.contacts.delete_all + end + + + #person deletion +# def delete_posts +# end + +# def delete_photos +# end + +# def comments +# end + +# def delete_notification_actors +# end + + def delete_posts + self.person.posts.delete_all + end + + def delete_photos + self.person.photos.delete_all + end + + def delete_mentions + self.person.mentions.delete_all + end + +# def reset_profile +# end + + def delete_contacts_of_me + Contact.all_contacts_of_person(self.person).delete_all + end + #private + +end diff --git a/app/models/contact.rb b/app/models/contact.rb index 518b11e89..8eae319fb 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -23,7 +23,10 @@ class Contact < ActiveRecord::Base before_destroy :destroy_notifications, :repopulate_cache! - # contact.sharing is true when contact.person is sharing with contact.user + + scope :all_contacts_of_person, lambda {|x| where(:person_id => x.id)} + + # contact.sharing is true when contact.person is sharing with contact.user scope :sharing, lambda { where(:sharing => true) } diff --git a/app/models/invitation.rb b/app/models/invitation.rb index 3be97a991..fc364b16c 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -99,6 +99,17 @@ class Invitation < ActiveRecord::Base self end + + # converts a personal invitation to an admin invite + # used in account deletion + # @return [Invitation] self + def convert_to_admin! + self.admin = true + self.sender = nil + self.aspect = nil + self.save + self + end # @return [Invitation] self def resend self.send! diff --git a/app/models/user.rb b/app/models/user.rb index 654bb7bb1..e49025acd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,6 +44,7 @@ class User < ActiveRecord::Base has_many :tag_followings, :dependent => :destroy has_many :followed_tags, :through => :tag_followings, :source => :tag, :order => 'tags.name' has_many :blocks + has_many :notifications, :foreign_key => :recipient_id has_many :authorizations, :class_name => 'OAuth2::Provider::Models::ActiveRecord::Authorization', :foreign_key => :resource_owner_id has_many :applications, :through => :authorizations, :source => :client diff --git a/spec/factories.rb b/spec/factories.rb index 74578cf6e..24af78f8c 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -166,3 +166,22 @@ end Factory.define(:oauth_access_token, :class => OAuth2::Provider.access_token_class) do |a| a.association(:authorization, :factory => :oauth_authorization) end + +Factory.define(:tag, :class => ActsAsTaggableOn::Tag) do |t| + t.name "partytimeexcellent" +end + +Factory.define(:tag_following) do |a| + a.association(:tag, :factory => :tag) + a.association(:user, :factory => :user) +end + +Factory.define(:contact) do |c| + c.association(:person, :factory => :person) + c.association(:user, :factory => :user) +end + +Factory.define(:mention) do |c| + c.association(:person, :factory => :person) + c.association(:post, :factory => :status_message) +end diff --git a/spec/helper_methods.rb b/spec/helper_methods.rb index 4c2fa3196..921b7ffbc 100644 --- a/spec/helper_methods.rb +++ b/spec/helper_methods.rb @@ -61,4 +61,15 @@ module HelperMethods fixture_name = File.join(File.dirname(__FILE__), 'fixtures', fixture_filename) File.open(fixture_name) end + + def create_conversation_with_message(sender, recipient, subject, text) + create_hash = { + :author => sender.person, + :participant_ids => [sender.person.id, recipient.person.id], + :subject => subject, + :messages_attributes => [ {:author => sender.person, :text => text} ] + } + + Conversation.create!(create_hash) + end end diff --git a/spec/integration/account_deletion_spec.rb b/spec/integration/account_deletion_spec.rb new file mode 100644 index 000000000..f442710f0 --- /dev/null +++ b/spec/integration/account_deletion_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'deleteing your account' do + before do + @bob2 = bob + @bobs_person_id = @bob2.person.id + @alices_post = alice.post(:status_message, :text => "@{@bob2 Grimn; #{@bob2.person.diaspora_handle}} you are silly", :to => alice.aspects.find_by_name('generic')) + + @bobs_contact_ids = @bob2.contacts.map {|c| c.id} + + #@bob2's own content + @bob2.post(:status_message, :text => 'asldkfjs', :to => @bob2.aspects.first) + f = Factory(:photo, :author => @bob2.person) + + #objects on post + @bob2.like(true, :target => @alices_post) + @bob2.comment("here are some thoughts on your post", :post => @alices_post) + + #conversations + create_conversation_with_message(alice, @bob2, "Subject", "Hey @bob2") + + AccountDeletion.new(@bob2.person.diaspora_handle).perform! + + @bob2.reload + end + + it 'deletes all of @bob2s posts' do + @bob2.posts.should be_empty + end + + it 'deletes all of @bob2s share visiblites' do + ShareVisibility.where(:contact_id => @bobs_contact_ids).should be_empty + end + + it 'deletes all photos' do + Photo.where(:author_id => @bobs_person_id).should be_empty + end + + it 'deletes all mentions ' do + @bob2.person.mentions.should be_empty + end + + it 'deletes all aspects' do + @bob2.aspects.should be_empty + end + + it 'deletes all contacts' do + @bob2.contacts.should be_empty + end + + it 'deletes the converersation visibilities' do + pending + end +end diff --git a/spec/misc_spec.rb b/spec/misc_spec.rb index 1c5ce9edb..8007c3753 100644 --- a/spec/misc_spec.rb +++ b/spec/misc_spec.rb @@ -61,4 +61,22 @@ describe 'making sure the spec runner works' do alice.comment "yo", :post => person_status end end + + describe '#post' do + it 'creates a notification with a mention' do + lambda{ + alice.post(:status_message, :text => "@{Bob Grimn; #{bob.person.diaspora_handle}} you are silly", :to => alice.aspects.find_by_name('generic')) + }.should change(Notification, :count).by(1) + end + end + + describe "#create_conversation_with_message" do + it 'creates a conversation and a message' do + conversation = create_conversation_with_message(alice, bob, "Subject", "Hey Bob") + + conversation.participants.should == [alice.person, bob.person] + conversation.subject.should == "Subject" + conversation.messages.first.text.should == "Hey Bob" + end + end end diff --git a/spec/models/account_deletion_spec.rb b/spec/models/account_deletion_spec.rb new file mode 100644 index 000000000..a75ecd5ee --- /dev/null +++ b/spec/models/account_deletion_spec.rb @@ -0,0 +1,130 @@ +# Copyright (c) 2010-2011, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +require 'spec_helper' + +describe AccountDeletion do + before do + @account_deletion = AccountDeletion.new(bob.person.diaspora_handle) + @account_deletion.user = bob + end + + it 'works' do + pending + end + + it "attaches the user" do + AccountDeletion.new(bob.person.diaspora_handle).user.should == bob + AccountDeletion.new(remote_raphael.diaspora_handle).user.should == nil + end + + describe '#perform' do + it 'calls delete_standard_associations' do + @account_deletion.should_receive(:delete_standard_associations) + @account_deletion.perform! + end + + it 'calls disassociate_invitations' do + @account_deletion.should_receive(:disassociate_invitations) + @account_deletion.perform! + end + + it 'calls delete_contacts_of_me' do + @account_deletion.should_receive(:delete_contacts_of_me) + @account_deletion.perform! + end + + it 'calls delete_contacts_of_me' do + @account_deletion.should_receive(:delete_mentions) + @account_deletion.perform! + end + + it 'calls disconnect_contacts' do + @account_deletion.should_receive(:disconnect_contacts) + @account_deletion.perform! + end + + it 'calls delete_posts' do + @account_deletion.should_receive(:delete_posts) + @account_deletion.perform! + end + end + + describe "#delete_standard_associations" do + it 'removes all standard user associaltions' do + + @account_deletion.normal_ar_user_associates_to_delete.each do |asso| + association_mock = mock + association_mock.should_receive(:delete_all) + bob.should_receive(asso).and_return(association_mock) + end + + @account_deletion.delete_standard_associations + end + end + + + describe '#delete_posts' do + it 'deletes all posts' do + @account_deletion.person.posts.should_receive(:delete_all) + @account_deletion.delete_posts + end + end + + describe '#delete_photos' do + it 'deletes all photos' do + @account_deletion.person.photos.should_receive(:delete_all) + @account_deletion.delete_posts + end + end + + describe "#disassociate_invitations" do + it "sets invitations_from_me to be admin invitations" do + invites = [mock] + bob.stub(:invitations_from_me).and_return(invites) + invites.first.should_receive(:convert_to_admin!) + @account_deletion.disassociate_invitations + end + end + + describe "#normal_ar_user_associates_to_delete" do + it "has the regular associations" do + @account_deletion.normal_ar_user_associates_to_delete.should == + [:tag_followings, :authorizations, :invitations_to_me, :services, :aspects, :user_preferences, :notifications] + end + end + + context 'person associations' do + describe '#delete mentions' do + it 'deletes the mentions for people' do + mentions = mock + @account_deletion.person.should_receive(:mentions).and_return(mentions) + mentions.should_receive(:delete_all) + @account_deletion.delete_mentions + end + end + + describe '#disconnect_contacts' do + it "deletes all of user's contacts" do + bob.contacts.should_receive(:delete_all) + @account_deletion.disconnect_contacts + end + end + + describe '#delete_contacts_of_me' do + it 'deletes all the local contact objects where deleted account is the person' do + contacts = mock + Contact.should_receive(:all_contacts_of_person).with(bob.person).and_return(contacts) + contacts.should_receive(:delete_all) + @account_deletion.delete_contacts_of_me + end + end + end + + it 'has all user association keys accounted for' do + all_keys = (@account_deletion.normal_ar_user_associates_to_delete + @account_deletion.special_ar_user_associations + @account_deletion.ignored_ar_user_associations) + all_keys.sort{|x, y| x.to_s <=> y.to_s}.should == User.reflections.keys.sort{|x, y| x.to_s <=> y.to_s} + end +end + diff --git a/spec/models/contact_spec.rb b/spec/models/contact_spec.rb index 1a3c507aa..632969382 100644 --- a/spec/models/contact_spec.rb +++ b/spec/models/contact_spec.rb @@ -81,6 +81,16 @@ describe Contact do }.by(2) end end + + describe "all_contacts_of_person" do + it 'returns all contacts where the person is the passed in person' do + person = Factory.create(:person) + contact1 = Factory(:contact, :person => person) + contact2 = Factory(:contact) + contacts = Contact.all_contacts_of_person(person) + contacts.should == [contact1] + end + end end describe '#contacts' do diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb index 4681ba9c5..ad19d8b59 100644 --- a/spec/models/invitation_spec.rb +++ b/spec/models/invitation_spec.rb @@ -75,6 +75,17 @@ describe Invitation do }.should_not change(User, :count) end end + + describe '#convert_to_admin!' do + it 'reset sender and aspect to nil, and sets admin flag to true' do + invite = Factory(:invitation) + invite.convert_to_admin! + invite.reload + invite.admin?.should be_true + invite.sender_id.should be_nil + invite.aspect_id.should be_nil + end + end describe '.batch_invite' do before do diff --git a/spec/models/tag_following_spec.rb b/spec/models/tag_following_spec.rb index 3b1614dfa..611c4fee3 100644 --- a/spec/models/tag_following_spec.rb +++ b/spec/models/tag_following_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe TagFollowing do before do - @tag = ActsAsTaggableOn::Tag.create(:name => "partytimeexcellent") + @tag = Factory.create(:tag) TagFollowing.create!(:tag => @tag, :user => alice) end