summaryrefslogtreecommitdiff
path: root/spec/services/database/consistency_check_service_spec.rb
blob: 2e64245143259faa4f8b7c403c6d5ece9d2ed94f (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Database::ConsistencyCheckService do
  let(:batch_size) { 5 }
  let(:max_batches) { 2 }

  before do
    stub_const("Gitlab::Database::ConsistencyChecker::BATCH_SIZE", batch_size)
    stub_const("Gitlab::Database::ConsistencyChecker::MAX_BATCHES", max_batches)
  end

  after do
    redis_shared_state_cleanup!
  end

  subject(:consistency_check_service) do
    described_class.new(
      source_model: Namespace,
      target_model: Ci::NamespaceMirror,
      source_columns: %w[id traversal_ids],
      target_columns: %w[namespace_id traversal_ids]
    )
  end

  describe '#random_start_id' do
    let(:batch_size) { 5 }

    before do
      create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects
    end

    it 'generates a random start_id within the records ids' do
      10.times do
        start_id = subject.send(:random_start_id)
        expect(start_id).to be_between(Namespace.first.id, Namespace.last.id).inclusive
      end
    end
  end

  describe '#execute' do
    let(:empty_results) do
      { batches: 0, matches: 0, mismatches: 0, mismatches_details: [] }
    end

    context 'when empty tables' do
      it 'returns results with zero counters' do
        result = consistency_check_service.execute

        expect(result).to eq(empty_results)
      end

      it 'does not call the ConsistencyCheckService' do
        expect(Gitlab::Database::ConsistencyChecker).not_to receive(:new)
        consistency_check_service.execute
      end
    end

    context 'no cursor has been saved before' do
      let(:selected_start_id) { Namespace.order(:id).limit(5).pluck(:id).last }
      let(:expected_next_start_id) { selected_start_id + batch_size * max_batches }

      before do
        create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects
        expect(consistency_check_service).to receive(:random_start_id).and_return(selected_start_id)
      end

      it 'picks a random start_id' do
        expected_result = {
          batches: 2,
          matches: 10,
          mismatches: 0,
          mismatches_details: [],
          start_id: selected_start_id,
          next_start_id: expected_next_start_id
        }
        expect(consistency_check_service.execute).to eq(expected_result)
      end

      it 'calls the ConsistencyCheckService with the expected parameters' do
        allow_next_instance_of(Gitlab::Database::ConsistencyChecker) do |instance|
          expect(instance).to receive(:execute).with(start_id: selected_start_id).and_return({
            batches: 2,
            next_start_id: expected_next_start_id,
            matches: 10,
            mismatches: 0,
            mismatches_details: []
          })
        end

        expect(Gitlab::Database::ConsistencyChecker).to receive(:new).with(
          source_model: Namespace,
          target_model: Ci::NamespaceMirror,
          source_columns: %w[id traversal_ids],
          target_columns: %w[namespace_id traversal_ids]
        ).and_call_original

        expected_result = {
          batches: 2,
          start_id: selected_start_id,
          next_start_id: expected_next_start_id,
          matches: 10,
          mismatches: 0,
          mismatches_details: []
        }
        expect(consistency_check_service.execute).to eq(expected_result)
      end

      it 'saves the next_start_id in Redis for he next iteration' do
        expect(consistency_check_service).to receive(:save_next_start_id).with(expected_next_start_id).and_call_original
        consistency_check_service.execute
      end
    end

    context 'cursor saved in Redis and moving' do
      let(:first_namespace_id) { Namespace.order(:id).first.id }
      let(:second_namespace_id) { Namespace.order(:id).second.id }

      before do
        create_list(:namespace, 30) # This will also create Ci::NameSpaceMirror objects
      end

      it "keeps moving the cursor with each call to the service" do
        expect(consistency_check_service).to receive(:random_start_id).at_most(:once).and_return(first_namespace_id)

        allow_next_instance_of(Gitlab::Database::ConsistencyChecker) do |instance|
          expect(instance).to receive(:execute).ordered.with(start_id: first_namespace_id).and_call_original
          expect(instance).to receive(:execute).ordered.with(start_id: first_namespace_id + 10).and_call_original
          expect(instance).to receive(:execute).ordered.with(start_id: first_namespace_id + 20).and_call_original
          # Gets back to the start of the table
          expect(instance).to receive(:execute).ordered.with(start_id: first_namespace_id).and_call_original
        end

        4.times do
          consistency_check_service.execute
        end
      end

      it "keeps moving the cursor from any start point" do
        expect(consistency_check_service).to receive(:random_start_id).at_most(:once).and_return(second_namespace_id)

        allow_next_instance_of(Gitlab::Database::ConsistencyChecker) do |instance|
          expect(instance).to receive(:execute).ordered.with(start_id: second_namespace_id).and_call_original
          expect(instance).to receive(:execute).ordered.with(start_id: second_namespace_id + 10).and_call_original
        end

        2.times do
          consistency_check_service.execute
        end
      end
    end
  end
end