summaryrefslogtreecommitdiff
path: root/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
blob: 06b6d5e3b462b1faedd08cba98432d2b008c132e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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')

LOST_AND_FOUND_GROUP = 'lost-and-found'
USER_TYPE_GHOST = 5
ACCESS_LEVEL_OWNER = 50

# 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
    expect(users.where(user_type: USER_TYPE_GHOST).count).to eq(0)

    disable_migrations_output { migrate! }

    expect(users.where(user_type: USER_TYPE_GHOST).count).to eq(1)
  end

  it 'creates the lost-and-found group, owned by the ghost user' do
    expect(
      Group.where(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%")).count
    ).to eq(0)

    disable_migrations_output { migrate! }

    ghost_user = users.find_by(user_type: USER_TYPE_GHOST)
    expect(
      Group
        .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')
        .where('members.access_level = ?', ACCESS_LEVEL_OWNER)
        .where(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%"))
        .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! }

    lost_and_found_group = Group.find_by(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%"))
    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