gitlab-org--gitlab-foss/spec/migrations
Luke Duncalfe 177463007b Migrations for adding issue_id to versions table
These migrations do the following:

- Adds a new `issue_id` column to `versions`. This fixes an n+1 problem
  when loading versions for an issue in GraphQL as AR can now load from
  cache
- Change the unique restraint on versions.sha to be scoped to `issue_id`
  as in order to import version data, we need to allow duplicate `sha`
  values for versions
- Update all versions with an `issue_id`

https://gitlab.com/gitlab-org/gitlab-ee/issues/11090
2019-07-29 18:55:19 +00:00
..
active_record
add_foreign_key_from_notification_settings_to_users_spec.rb
add_foreign_keys_to_todos_spec.rb
add_not_null_constraint_to_project_mirror_data_foreign_key_spec.rb
add_pages_access_level_to_project_feature_spec.rb
add_pipeline_build_foreign_key_spec.rb
add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
add_unique_constraint_to_project_features_project_id_spec.rb
assure_commits_count_for_merge_request_diff_spec.rb
backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
backfill_releases_name_with_tag_name_spec.rb
backfill_store_project_full_path_in_repo_spec.rb
backport_enterprise_schema_spec.rb
change_default_value_for_dsa_key_restriction_spec.rb
change_outbound_local_requests_whitelist_default_spec.rb
change_packages_size_defaults_in_project_statistics_spec.rb
clean_up_noteable_id_for_notes_on_commits_spec.rb
cleanup_build_stage_migration_spec.rb
cleanup_environments_external_url_spec.rb
cleanup_legacy_artifact_migration_spec.rb
cleanup_stages_position_migration_spec.rb
create_missing_namespace_for_internal_users_spec.rb
drop_duplicate_protected_tags_spec.rb
encrypt_feature_flags_clients_tokens_spec.rb
enqueue_reset_merge_status_second_run_spec.rb
enqueue_reset_merge_status_spec.rb
enqueue_verify_pages_domain_workers_spec.rb
fill_empty_finished_at_in_deployments_spec.rb
fill_file_store_spec.rb
fix_null_type_labels_spec.rb
fix_pool_repository_source_project_id_spec.rb
fix_wrong_pages_access_level_spec.rb
generate_lets_encrypt_private_key_spec.rb
generate_missing_routes_spec.rb
import_common_metrics_spec.rb
migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
migrate_cluster_configure_worker_sidekiq_queue_spec.rb
migrate_create_trace_artifact_sidekiq_queue_spec.rb
migrate_forbidden_redirect_uris_spec.rb
migrate_k8s_service_integration_spec.rb
migrate_legacy_artifacts_to_job_artifacts_spec.rb
migrate_legacy_managed_clusters_to_unmanaged_spec.rb
migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
migrate_null_wiki_access_levels_spec.rb
migrate_object_storage_upload_sidekiq_queue_spec.rb
migrate_storage_migrator_sidekiq_queue_spec.rb
migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
populate_project_statistics_packages_size_spec.rb
populate_rule_type_on_approval_merge_request_rules_spec.rb
README.md
remove_empty_extern_uid_auth0_identities_spec.rb
remove_redundant_pipeline_stages_spec.rb
reschedule_builds_stages_migration_spec.rb
reschedule_commits_count_for_merge_request_diff_spec.rb
schedule_digest_personal_access_tokens_spec.rb
schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
schedule_merge_request_assignees_migration_progress_check_spec.rb
schedule_populate_merge_request_assignees_table_spec.rb
schedule_runners_token_encryption_spec.rb
schedule_set_confidential_note_events_on_webhooks_spec.rb
schedule_stages_index_migration_spec.rb
schedule_sync_issuables_state_id_spec.rb
schedule_sync_issuables_state_id_where_nil_spec.rb
schedule_to_archive_legacy_traces_spec.rb
set_issue_id_for_all_versions_spec.rb
steal_fill_store_upload_spec.rb
truncate_user_fullname_spec.rb
update_project_import_visibility_level_spec.rb

Testing migrations

In order to reliably test a migration, we need to test it against a database schema that this migration has been written for. In order to achieve that we have some migration helpers and RSpec test tag, called :migration.

If you want to write a test for a migration consider adding :migration tag to the test signature, like describe SomeMigrationClass, :migration.

How does it work?

Adding a :migration tag to a test signature injects a few before / after hooks to the test.

The most important change is that adding a :migration tag adds a before hook that will revert all migrations to the point that a migration under test is not yet migrated.

In other words, our custom RSpec hooks will find a previous migration, and migrate the database down to the previous migration version.

With this approach you can test a migration against a database schema that this migration has been written for.

The after hook will migrate the database up and reinstitutes the latest schema version, so that the process does not affect subsequent specs and ensures proper isolation.

Available helpers

Use table helper to create a temporary ActiveRecord::Base derived model for a table.

See spec/support/helpers/migrations_helpers.rb for all the available helpers.

Testing a class that is an ActiveRecord::Migration

In order to test a class that is an ActiveRecord::Migration, you will need to manually require the migration file because it is not autoloaded with Rails.

Use migrate! helper to run the migration that is under test. It will not only run migration, but will also bump the schema version in the schema_migrations table. It is necessary because in the after hook we trigger the rest of the migrations, and we need to know where to start.

Example

This spec tests the db/post_migrate/20170526185842_migrate_pipeline_stages.rb migration. You can find the complete spec on spec/migrations/migrate_pipeline_stages_spec.rb.

require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')

describe MigratePipelineStages, :migration do

  # Create test data - pipeline and CI/CD jobs.

  let(:jobs) { table(:ci_builds) }
  let(:stages) { table(:ci_stages) }
  let(:pipelines) { table(:ci_pipelines) }
  let(:projects) { table(:projects) }

  before do
    projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
    pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
    jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
    jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
  end

  # Test the migration.

  it 'correctly migrates pipeline stages' do
    expect(stages.count).to be_zero

    migrate!

    expect(stages.count).to eq 2
    expect(stages.all.pluck(:name)).to match_array %w[test build]
  end
end

Testing a class that is not an ActiveRecord::Migration

To test a class that is not an ActiveRecord::Migration (a background migration), you will need to manually provide a required schema version. Please add a schema tag to a context that you want to switch the database schema within.

Example: describe SomeClass, :migration, schema: 20170608152748.

Example

This spec tests the lib/gitlab/background_migration/archive_legacy_traces.rb background migration. You can find the complete spec on spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb

require 'spec_helper'

describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
  include TraceHelpers

  let(:namespaces) { table(:namespaces) }
  let(:projects) { table(:projects) }
  let(:builds) { table(:ci_builds) }
  let(:job_artifacts) { table(:ci_job_artifacts) }

  before do
    namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
    projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123)
    @build = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build')
  end

  context 'when trace file exsits at the right place' do
    before do
      create_legacy_trace(@build, 'trace in file')
    end

    it 'correctly archive legacy traces' do
      expect(job_artifacts.count).to eq(0)
      expect(File.exist?(legacy_trace_path(@build))).to be_truthy

      described_class.new.perform(1, 1)

      expect(job_artifacts.count).to eq(1)
      expect(File.exist?(legacy_trace_path(@build))).to be_falsy
      expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in file')
    end
  end
end

Best practices

  1. Note that this type of tests do not run within the transaction, we use a deletion database cleanup strategy. Do not depend on transaction being present.