2020-05-19 09:08:12 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
require Rails.root.join('db', 'post_migrate', '20200511080113_add_projects_foreign_key_to_namespaces.rb')
|
|
|
|
require Rails.root.join('db', 'post_migrate', '20200511083541_cleanup_projects_with_missing_namespace.rb')
|
|
|
|
|
|
|
|
# In order to test the CleanupProjectsWithMissingNamespace migration, we need
|
|
|
|
# to first create an orphaned project (one with an invalid namespace_id)
|
|
|
|
# and then run the migration to check that the project was properly cleaned up
|
|
|
|
#
|
|
|
|
# The problem is that the CleanupProjectsWithMissingNamespace migration comes
|
|
|
|
# after the FK has been added with a previous migration (AddProjectsForeignKeyToNamespaces)
|
|
|
|
# That means that while testing the current class we can not insert projects with an
|
|
|
|
# invalid namespace_id as the existing FK is correctly blocking us from doing so
|
|
|
|
#
|
|
|
|
# The approach that solves that problem is to:
|
|
|
|
# - Set the schema of this test to the one prior to AddProjectsForeignKeyToNamespaces
|
|
|
|
# - We could hardcode it to `20200508091106` (which currently is the previous
|
|
|
|
# migration before adding the FK) but that would mean that this test depends
|
|
|
|
# on migration 20200508091106 not being reverted or deleted
|
|
|
|
# - So, we use SchemaVersionFinder that finds the previous migration and returns
|
|
|
|
# its schema, which we then use in the describe
|
|
|
|
#
|
|
|
|
# That means that we lock the schema version to the one returned by
|
|
|
|
# SchemaVersionFinder.previous_migration and only test the cleanup migration
|
|
|
|
# *without* the migration that adds the Foreign Key ever running
|
|
|
|
# That's acceptable as the cleanup script should not be affected in any way
|
|
|
|
# by the migration that adds the Foreign Key
|
|
|
|
class SchemaVersionFinder
|
|
|
|
def self.migrations_paths
|
|
|
|
ActiveRecord::Migrator.migrations_paths
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.migration_context
|
|
|
|
ActiveRecord::MigrationContext.new(migrations_paths, ActiveRecord::SchemaMigration)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.migrations
|
|
|
|
migration_context.migrations
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.previous_migration
|
|
|
|
migrations.each_cons(2) do |previous, migration|
|
|
|
|
break previous.version if migration.name == AddProjectsForeignKeyToNamespaces.name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionFinder.previous_migration do
|
|
|
|
let(:projects) { table(:projects) }
|
|
|
|
let(:namespaces) { table(:namespaces) }
|
|
|
|
let(:users) { table(:users) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
namespace = namespaces.create!(name: 'existing_namespace', path: 'existing_namespace')
|
|
|
|
|
|
|
|
projects.create!(
|
|
|
|
name: 'project_with_existing_namespace',
|
|
|
|
path: 'project_with_existing_namespace',
|
|
|
|
visibility_level: 20,
|
|
|
|
archived: false,
|
|
|
|
namespace_id: namespace.id
|
|
|
|
)
|
|
|
|
|
|
|
|
projects.create!(
|
|
|
|
name: 'project_with_non_existing_namespace',
|
|
|
|
path: 'project_with_non_existing_namespace',
|
|
|
|
visibility_level: 20,
|
|
|
|
archived: false,
|
|
|
|
namespace_id: non_existing_record_id
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates the ghost user' do
|
2020-06-16 12:09:00 +00:00
|
|
|
expect(users.where(user_type: described_class::User::USER_TYPE_GHOST).count).to eq(0)
|
2020-05-19 09:08:12 +00:00
|
|
|
|
|
|
|
disable_migrations_output { migrate! }
|
|
|
|
|
2020-06-16 12:09:00 +00:00
|
|
|
expect(users.where(user_type: described_class::User::USER_TYPE_GHOST).count).to eq(1)
|
2020-05-19 09:08:12 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates the lost-and-found group, owned by the ghost user' do
|
|
|
|
expect(
|
2020-06-16 12:09:00 +00:00
|
|
|
described_class::Group.where(
|
|
|
|
described_class::Group
|
|
|
|
.arel_table[:name]
|
|
|
|
.matches("#{described_class::User::LOST_AND_FOUND_GROUP}%")
|
|
|
|
).count
|
2020-05-19 09:08:12 +00:00
|
|
|
).to eq(0)
|
|
|
|
|
|
|
|
disable_migrations_output { migrate! }
|
|
|
|
|
2020-06-16 12:09:00 +00:00
|
|
|
ghost_user = users.find_by(user_type: described_class::User::USER_TYPE_GHOST)
|
2020-05-19 09:08:12 +00:00
|
|
|
expect(
|
2020-06-16 12:09:00 +00:00
|
|
|
described_class::Group
|
2020-05-19 09:08:12 +00:00
|
|
|
.joins('INNER JOIN members ON namespaces.id = members.source_id')
|
|
|
|
.where('namespaces.type = ?', 'Group')
|
|
|
|
.where('members.type = ?', 'GroupMember')
|
|
|
|
.where('members.source_type = ?', 'Namespace')
|
|
|
|
.where('members.user_id = ?', ghost_user.id)
|
|
|
|
.where('members.requested_at IS NULL')
|
2020-06-16 12:09:00 +00:00
|
|
|
.where('members.access_level = ?', described_class::ACCESS_LEVEL_OWNER)
|
|
|
|
.where(
|
|
|
|
described_class::Group
|
|
|
|
.arel_table[:name]
|
|
|
|
.matches("#{described_class::User::LOST_AND_FOUND_GROUP}%")
|
|
|
|
)
|
2020-05-19 09:08:12 +00:00
|
|
|
.count
|
|
|
|
).to eq(1)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'moves the orphaned project to the lost-and-found group' do
|
|
|
|
orphaned_project = projects.find_by(name: 'project_with_non_existing_namespace')
|
|
|
|
expect(orphaned_project.visibility_level).to eq(20)
|
|
|
|
expect(orphaned_project.archived).to eq(false)
|
|
|
|
expect(orphaned_project.namespace_id).to eq(non_existing_record_id)
|
|
|
|
|
|
|
|
disable_migrations_output { migrate! }
|
|
|
|
|
2020-06-16 12:09:00 +00:00
|
|
|
lost_and_found_group = described_class::Group.find_by(
|
|
|
|
described_class::Group
|
|
|
|
.arel_table[:name]
|
|
|
|
.matches("#{described_class::User::LOST_AND_FOUND_GROUP}%")
|
|
|
|
)
|
2020-05-19 09:08:12 +00:00
|
|
|
orphaned_project = projects.find_by(id: orphaned_project.id)
|
|
|
|
|
|
|
|
expect(orphaned_project.visibility_level).to eq(0)
|
|
|
|
expect(orphaned_project.namespace_id).to eq(lost_and_found_group.id)
|
|
|
|
expect(orphaned_project.name).to eq("project_with_non_existing_namespace_#{orphaned_project.id}")
|
|
|
|
expect(orphaned_project.path).to eq("project_with_non_existing_namespace_#{orphaned_project.id}")
|
|
|
|
expect(orphaned_project.archived).to eq(true)
|
|
|
|
|
|
|
|
valid_project = projects.find_by(name: 'project_with_existing_namespace')
|
|
|
|
existing_namespace = namespaces.find_by(name: 'existing_namespace')
|
|
|
|
|
|
|
|
expect(valid_project.visibility_level).to eq(20)
|
|
|
|
expect(valid_project.namespace_id).to eq(existing_namespace.id)
|
|
|
|
expect(valid_project.path).to eq('project_with_existing_namespace')
|
|
|
|
expect(valid_project.archived).to eq(false)
|
|
|
|
end
|
|
|
|
end
|