mastodon/lib/tasks/tests.rake

508 lines
20 KiB
Ruby

# frozen_string_literal: true
namespace :tests do
namespace :migrations do
desc 'Prepares all migrations and test data for consistency checks'
task prepare_database: :environment do
{
'2' => 2017_10_10_025614,
'2_4' => 2018_05_14_140000,
'2_4_3' => 2018_07_07_154237,
'3_3_0' => 2020_12_18_054746,
}.each do |release, version|
ActiveRecord::Tasks::DatabaseTasks
.migration_connection
.migration_context
.migrate(version)
Rake::Task["tests:migrations:populate_v#{release}"]
.invoke
end
end
desc 'Check that database state is consistent with a successful migration from populated data'
task check_database: :environment do
unless Account.find_by(username: 'admin', domain: nil)&.hide_collections? == false
puts 'Unexpected value for Account#hide_collections? for user @admin'
exit(1)
end
unless Account.find_by(username: 'user', domain: nil)&.hide_collections? == true
puts 'Unexpected value for Account#hide_collections? for user @user'
exit(1)
end
unless Account.find_by(username: 'evil', domain: 'activitypub.com')&.suspended?
puts 'Unexpected value for Account#suspended? for user @evil@activitypub.com'
exit(1)
end
unless Status.find(6).account_id == Status.find(7).account_id
puts 'Users @remote@remote.com and @Remote@remote.com not properly merged'
exit(1)
end
if Account.exists?(domain: Rails.configuration.x.local_domain)
puts 'Faux remote accounts not properly cleaned up'
exit(1)
end
unless AccountConversation.first&.last_status_id == 11
puts 'AccountConversation records not created as expected'
exit(1)
end
if Account.find(Account::INSTANCE_ACTOR_ID).private_key.blank?
puts 'Instance actor does not have a private key'
exit(1)
end
unless Account.find_by(username: 'user', domain: nil).custom_filters.map { |filter| filter.keywords.pluck(:keyword) } == [['test'], ['take']]
puts 'CustomFilterKeyword records not created as expected'
exit(1)
end
unless Admin::ActionLog.find_by(target_type: 'DomainBlock', target_id: 1).human_identifier == 'example.org'
puts 'Admin::ActionLog domain block records not updated as expected'
exit(1)
end
unless Admin::ActionLog.find_by(target_type: 'EmailDomainBlock', target_id: 1).human_identifier == 'example.org'
puts 'Admin::ActionLog email domain block records not updated as expected'
exit(1)
end
unless User.find(1).settings['notification_emails.favourite'] == true && User.find(1).settings['notification_emails.mention'] == false
puts 'User settings not kept as expected'
exit(1)
end
unless User.find(1).settings['web.trends'] == false
puts 'User settings not kept as expected'
exit(1)
end
unless Account.find_remote('bob', 'ActivityPub.com').domain == 'activitypub.com'
puts 'Account domains not properly normalized'
exit(1)
end
unless PreviewCard.where(id: PreviewCardsStatus.where(status_id: 12).select(:preview_card_id)).pluck(:url) == ['https://joinmastodon.org/']
puts 'Preview cards not deduplicated as expected'
exit(1)
end
unless Account.find_local('kmruser').user.chosen_languages == %w(en ku ckb)
puts 'Chosen languages not migrated as expected for kmr users'
exit(1)
end
unless Account.find_local('kmruser').user.settings['default_language'] == 'ku'
puts 'Default posting language not migrated as expected for kmr users'
exit(1)
end
unless Account.find_local('qcuser').user.locale == 'fr-CA'
puts 'Locale for fr-QC users not updated to fr-CA as expected'
exit(1)
end
policy = NotificationPolicy.find_by(account: User.find(1).account)
unless policy.for_private_mentions == 'accept' && policy.for_not_following == 'filter'
puts "Notification policy not migrated as expected: #{policy.for_private_mentions.inspect}, #{policy.for_not_following.inspect}"
exit(1)
end
unless Identity.where(provider: 'foo', uid: 0).count == 1
puts 'Identities not deduplicated as expected'
exit(1)
end
unless WebauthnCredential.where(user_id: 1, nickname: 'foo').count == 1
puts 'Webauthn credentials not deduplicated as expected'
exit(1)
end
unless AccountAlias.where(account_id: 1, uri: 'https://example.com/users/foobar').count == 1
puts 'Account aliases not deduplicated as expected'
exit(1)
end
# This is checking the attribute rather than the method, to avoid the legacy fallback
# and ensure the data has been migrated
unless Account.find_local('qcuser').user[:otp_secret] == 'anotpsecretthatshouldbeencrypted'
puts 'OTP secret for user not preserved as expected'
exit(1)
end
unless Doorkeeper::Application.find(2)[:scopes] == 'write:accounts profile'
puts 'Application OAuth scopes not rewritten as expected'
exit(1)
end
unless Doorkeeper::Application.find(2).access_tokens.first[:scopes] == 'write:accounts profile'
puts 'OAuth access token scopes not rewritten as expected'
exit(1)
end
puts 'No errors found. Database state is consistent with a successful migration process.'
end
desc 'Populate the database with test data for 3.3.0'
task populate_v3_3_0: :environment do # rubocop:disable Naming/VariableNumber
ActiveRecord::Base.connection.execute(<<~SQL.squish)
INSERT INTO "webauthn_credentials"
(user_id, nickname, external_id, public_key, created_at, updated_at)
VALUES
(1, 'foo', 1, 'foo', now(), now()),
(1, 'foo', 2, 'bar', now(), now());
INSERT INTO "account_aliases"
(account_id, uri, acct, created_at, updated_at)
VALUES
(1, 'https://example.com/users/foobar', 'foobar@example.com', now(), now()),
(1, 'https://example.com/users/foobar', 'foobar@example.com', now(), now());
/* Doorkeeper records
While the `read:me` scope was technically not valid in 3.3.0,
it is still useful for the purposes of testing the `ChangeReadMeScopeToProfile`
migration.
*/
INSERT INTO "oauth_applications"
(id, name, uid, secret, redirect_uri, scopes, created_at, updated_at)
VALUES
(2, 'foo', 'foo', 'foo', 'https://example.com/#foo', 'write:accounts read:me', now(), now()),
(3, 'bar', 'bar', 'bar', 'https://example.com/#bar', 'read:me', now(), now());
INSERT INTO "oauth_access_tokens"
(token, application_id, scopes, resource_owner_id, created_at)
VALUES
('secret', 2, 'write:accounts read:me', 4, now());
SQL
end
desc 'Populate the database with test data for 2.4.3'
task populate_v2_4_3: :environment do # rubocop:disable Naming/VariableNumber
user_key = OpenSSL::PKey::RSA.new(2048)
user_private_key = ActiveRecord::Base.connection.quote(user_key.to_pem)
user_public_key = ActiveRecord::Base.connection.quote(user_key.public_key.to_pem)
ActiveRecord::Base.connection.execute(<<~SQL)
INSERT INTO "custom_filters"
(id, account_id, phrase, context, whole_word, irreversible, created_at, updated_at)
VALUES
(1, 2, 'test', '{ "home", "public" }', true, true, now(), now()),
(2, 2, 'take', '{ "home" }', false, false, now(), now());
-- Orphaned admin action logs
INSERT INTO "admin_action_logs"
(account_id, action, target_type, target_id, created_at, updated_at)
VALUES
(1, 'destroy', 'Account', 1312, now(), now()),
(1, 'destroy', 'User', 1312, now(), now()),
(1, 'destroy', 'Report', 1312, now(), now()),
(1, 'destroy', 'DomainBlock', 1312, now(), now()),
(1, 'destroy', 'EmailDomainBlock', 1312, now(), now()),
(1, 'destroy', 'Status', 1312, now(), now()),
(1, 'destroy', 'CustomEmoji', 1312, now(), now());
-- Admin action logs with linked objects
INSERT INTO "domain_blocks"
(id, domain, created_at, updated_at)
VALUES
(1, 'example.org', now(), now());
INSERT INTO "email_domain_blocks"
(id, domain, created_at, updated_at)
VALUES
(1, 'example.org', now(), now());
INSERT INTO "admin_action_logs"
(account_id, action, target_type, target_id, created_at, updated_at)
VALUES
(1, 'destroy', 'Account', 1, now(), now()),
(1, 'destroy', 'User', 1, now(), now()),
(1, 'destroy', 'DomainBlock', 1, now(), now()),
(1, 'destroy', 'EmailDomainBlock', 1, now(), now()),
(1, 'destroy', 'Status', 1, now(), now()),
(1, 'destroy', 'CustomEmoji', 3, now(), now());
INSERT INTO "settings"
(id, thing_type, thing_id, var, value, created_at, updated_at)
VALUES
(3, 'User', 1, 'notification_emails', E'--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nfollow: false\nreblog: true\nfavourite: true\nmention: false\nfollow_request: true\ndigest: true\nreport: true\npending_account: false\ntrending_tag: true\nappeal: true\n', now(), now()),
(4, 'User', 1, 'trends', E'--- false\n', now(), now());
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at)
VALUES
(10, 'kmruser', NULL, #{user_private_key}, #{user_public_key}, now(), now()),
(11, 'qcuser', NULL, #{user_private_key}, #{user_public_key}, now(), now());
INSERT INTO "users"
(id, account_id, email, created_at, updated_at, admin, locale, chosen_languages)
VALUES
(4, 10, 'kmruser@localhost', now(), now(), false, 'ku', '{en,kmr,ku,ckb}');
INSERT INTO "users"
(id, account_id, email, created_at, updated_at, locale,
encrypted_otp_secret, encrypted_otp_secret_iv, encrypted_otp_secret_salt,
otp_required_for_login)
VALUES
(5, 11, 'qcuser@localhost', now(), now(), 'fr-QC',
E'Fttsy7QAa0edaDfdfSz094rRLAxc8cJweDQ4BsWH/zozcdVA8o9GLqcKhn2b\nGi/V\n',
'rys3THICkr60BoWC',
'_LMkAGvdg7a+sDIKjI3mR2Q==',
true);
INSERT INTO "settings"
(id, thing_type, thing_id, var, value, created_at, updated_at)
VALUES
(5, 'User', 4, 'default_language', E'--- kmr\n', now(), now()),
(6, 'User', 1, 'interactions', E'--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nmust_be_follower: false\nmust_be_following: true\nmust_be_following_dm: false\n', now(), now());
INSERT INTO "identities"
(provider, uid, user_id, created_at, updated_at)
VALUES
('foo', 0, 1, now(), now()),
('foo', 0, 1, now(), now());
SQL
end
desc 'Populate the database with test data for 2.4.0'
task populate_v2_4: :environment do # rubocop:disable Naming/VariableNumber
ActiveRecord::Base.connection.execute(<<~SQL.squish)
INSERT INTO "settings"
(id, thing_type, thing_id, var, value, created_at, updated_at)
VALUES
(1, 'User', 1, 'hide_network', E'--- false\n', now(), now()),
(2, 'User', 2, 'hide_network', E'--- true\n', now(), now());
SQL
end
desc 'Populate the database with test data for 2.0.0'
task populate_v2: :environment do
admin_key = OpenSSL::PKey::RSA.new(2048)
user_key = OpenSSL::PKey::RSA.new(2048)
remote_key = OpenSSL::PKey::RSA.new(2048)
remote_key2 = OpenSSL::PKey::RSA.new(2048)
remote_key3 = OpenSSL::PKey::RSA.new(2048)
admin_private_key = ActiveRecord::Base.connection.quote(admin_key.to_pem)
admin_public_key = ActiveRecord::Base.connection.quote(admin_key.public_key.to_pem)
user_private_key = ActiveRecord::Base.connection.quote(user_key.to_pem)
user_public_key = ActiveRecord::Base.connection.quote(user_key.public_key.to_pem)
remote_public_key = ActiveRecord::Base.connection.quote(remote_key.public_key.to_pem)
remote_public_key2 = ActiveRecord::Base.connection.quote(remote_key2.public_key.to_pem)
remote_public_key_ap = ActiveRecord::Base.connection.quote(remote_key3.public_key.to_pem)
local_domain = ActiveRecord::Base.connection.quote(Rails.configuration.x.local_domain)
ActiveRecord::Base.connection.execute(<<~SQL)
-- accounts
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at)
VALUES
(1, 'admin', NULL, #{admin_private_key}, #{admin_public_key}, now(), now()),
(2, 'user', NULL, #{user_private_key}, #{user_public_key}, now(), now());
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at, remote_url, salmon_url)
VALUES
(3, 'remote', 'remote.com', NULL, #{remote_public_key}, now(), now(),
'https://remote.com/@remote', 'https://remote.com/salmon/1'),
(4, 'Remote', 'remote.com', NULL, #{remote_public_key}, now(), now(),
'https://remote.com/@Remote', 'https://remote.com/salmon/1'),
(5, 'REMOTE', 'Remote.com', NULL, #{remote_public_key2}, now() - interval '1 year', now() - interval '1 year',
'https://remote.com/stale/@REMOTE', 'https://remote.com/stale/salmon/1');
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at, protocol, inbox_url, outbox_url, followers_url)
VALUES
(6, 'bob', 'ActivityPub.com', NULL, #{remote_public_key_ap}, now(), now(),
1, 'https://activitypub.com/users/bob/inbox', 'https://activitypub.com/users/bob/outbox', 'https://activitypub.com/users/bob/followers');
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at)
VALUES
(7, 'user', #{local_domain}, #{user_private_key}, #{user_public_key}, now(), now()),
(8, 'pt_user', NULL, #{user_private_key}, #{user_public_key}, now(), now());
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at, protocol, inbox_url, outbox_url, followers_url, suspended)
VALUES
(9, 'evil', 'activitypub.com', NULL, #{remote_public_key_ap}, now(), now(),
1, 'https://activitypub.com/users/evil/inbox', 'https://activitypub.com/users/evil/outbox',
'https://activitypub.com/users/evil/followers', true);
-- users
INSERT INTO "users"
(id, account_id, email, created_at, updated_at, admin)
VALUES
(1, 1, 'admin@localhost', now(), now(), true),
(2, 2, 'user@localhost', now(), now(), false);
INSERT INTO "users"
(id, account_id, email, created_at, updated_at, admin, locale)
VALUES
(3, 8, 'ptuser@localhost', now(), now(), false, 'pt');
-- conversations
INSERT INTO "conversations" (id, created_at, updated_at) VALUES (1, now(), now());
-- statuses
INSERT INTO "statuses"
(id, account_id, text, created_at, updated_at)
VALUES
(1, 1, 'test', now(), now()),
(2, 1, '@remote@remote.com hello', now(), now()),
(3, 1, '@Remote@remote.com hello', now(), now()),
(4, 1, '@REMOTE@remote.com hello', now(), now());
INSERT INTO "statuses"
(id, account_id, text, created_at, updated_at, uri, local)
VALUES
(5, 1, 'activitypub status', now(), now(), 'https://localhost/users/admin/statuses/4', true);
INSERT INTO "statuses"
(id, account_id, text, created_at, updated_at)
VALUES
(6, 3, 'test', now(), now());
INSERT INTO "statuses"
(id, account_id, text, created_at, updated_at, in_reply_to_id, in_reply_to_account_id)
VALUES
(7, 4, '@admin hello', now(), now(), 3, 1);
INSERT INTO "statuses"
(id, account_id, text, created_at, updated_at)
VALUES
(8, 5, 'test', now(), now());
INSERT INTO "statuses"
(id, account_id, reblog_of_id, created_at, updated_at)
VALUES
(9, 1, 2, now(), now());
INSERT INTO "statuses"
(id, account_id, text, in_reply_to_id, conversation_id, visibility, created_at, updated_at)
VALUES
(10, 2, '@admin hey!', NULL, 1, 3, now(), now()),
(11, 1, '@user hey!', 10, 1, 3, now(), now());
INSERT INTO "statuses"
(id, account_id, text, created_at, updated_at)
VALUES
(12, 1, 'check out https://joinmastodon.org/', now(), now());
-- mentions (from previous statuses)
INSERT INTO "mentions"
(id, status_id, account_id, created_at, updated_at)
VALUES
(1, 2, 3, now(), now()),
(2, 3, 4, now(), now()),
(3, 4, 5, now(), now()),
(4, 10, 1, now(), now()),
(5, 11, 2, now(), now());
-- stream entries
INSERT INTO "stream_entries"
(activity_id, account_id, activity_type, created_at, updated_at)
VALUES
(1, 1, 'status', now(), now()),
(2, 1, 'status', now(), now()),
(3, 1, 'status', now(), now()),
(4, 1, 'status', now(), now()),
(5, 1, 'status', now(), now()),
(6, 3, 'status', now(), now()),
(7, 4, 'status', now(), now()),
(8, 5, 'status', now(), now()),
(9, 1, 'status', now(), now());
-- custom emoji
INSERT INTO "custom_emojis"
(id, shortcode, created_at, updated_at)
VALUES
(1, 'test', now(), now()),
(2, 'Test', now(), now()),
(3, 'blobcat', now(), now());
INSERT INTO "custom_emojis"
(id, shortcode, domain, uri, created_at, updated_at)
VALUES
(4, 'blobcat', 'remote.org', 'https://remote.org/emoji/blobcat', now(), now()),
(5, 'blobcat', 'Remote.org', 'https://remote.org/emoji/blobcat', now(), now()),
(6, 'Blobcat', 'remote.org', 'https://remote.org/emoji/Blobcat', now(), now());
-- favourites
INSERT INTO "favourites"
(account_id, status_id, created_at, updated_at)
VALUES
(1, 1, now(), now()),
(1, 7, now(), now()),
(4, 1, now(), now()),
(3, 1, now(), now()),
(5, 1, now(), now());
-- pinned statuses
INSERT INTO "status_pins"
(account_id, status_id, created_at, updated_at)
VALUES
(1, 1, now(), now()),
(3, 6, now(), now()),
(4, 7, now(), now());
-- follows
INSERT INTO "follows"
(id, account_id, target_account_id, created_at, updated_at)
VALUES
(1, 1, 5, now(), now()),
(2, 6, 2, now(), now()),
(3, 5, 2, now(), now()),
(4, 6, 1, now(), now());
-- follow requests
INSERT INTO "follow_requests"
(account_id, target_account_id, created_at, updated_at)
VALUES
(2, 5, now(), now()),
(5, 1, now(), now());
-- notifications
INSERT INTO "notifications"
(id, from_account_id, account_id, activity_type, activity_id, created_at, updated_at)
VALUES
(1, 6, 2, 'Follow', 2, now(), now()),
(2, 2, 1, 'Mention', 4, now(), now()),
(3, 1, 2, 'Mention', 5, now(), now());
-- preview cards
INSERT INTO "preview_cards"
(id, url, title, created_at, updated_at)
VALUES
(1, 'https://joinmastodon.org/', 'Mastodon - Decentralized social media', now(), now());
-- many-to-many association between preview cards and statuses
INSERT INTO "preview_cards_statuses"
(status_id, preview_card_id)
VALUES
(12, 1),
(12, 1);
SQL
end
end
end