internal_id_spec.rb 3.1 KB
Newer Older
1 2 3 4 5
require 'spec_helper'

describe InternalId do
  let(:project) { create(:project) }
  let(:usage) { :issues }
6
  let(:issue) { build(:issue, project: project) }
7
  let(:scope) { { project: project } }
8
  let(:init) { ->(s) { s.project.issues.size } }
9 10 11 12 13 14

  context 'validations' do
    it { is_expected.to validate_presence_of(:usage) }
  end

  describe '.generate_next' do
15
    subject { described_class.generate_next(issue, scope, usage, init) }
16

17
    context 'in the absence of a record' do
18 19 20 21 22 23 24 25 26
      it 'creates a record if not yet present' do
        expect { subject }.to change { described_class.count }.from(0).to(1)
      end

      it 'stores record attributes' do
        subject

        described_class.first.tap do |record|
          expect(record.project).to eq(project)
27
          expect(record.usage).to eq(usage.to_s)
28 29 30 31 32
        end
      end

      context 'with existing issues' do
        before do
33 34
          rand(1..10).times { create(:issue, project: project) }
          described_class.delete_all
35 36 37 38 39 40
        end

        it 'calculates last_value values automatically' do
          expect(subject).to eq(project.issues.size + 1)
        end
      end
41 42 43 44 45 46 47 48 49 50 51 52 53

      context 'with concurrent inserts on table' do
        it 'looks up the record if it was created concurrently' do
          args = { **scope, usage: described_class.usages[usage.to_s] }
          record = double
          expect(described_class).to receive(:find_by).with(args).and_return(nil)    # first call, record not present
          expect(described_class).to receive(:find_by).with(args).and_return(record) # second call, record was created by another process
          expect(described_class).to receive(:create!).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
          expect(record).to receive(:increment_and_save!)

          subject
        end
      end
54 55 56
    end

    it 'generates a strictly monotone, gapless sequence' do
A
Andreas Brandl 已提交
57
      seq = (0..rand(100)).map do
58
        described_class.generate_next(issue, scope, usage, init)
59 60
      end
      normalized = seq.map { |i| i - seq.min }
A
Andreas Brandl 已提交
61

62 63
      expect(normalized).to eq((0..seq.size - 1).to_a)
    end
64 65 66 67 68 69 70 71 72 73 74

    context 'with an insufficient schema version' do
      before do
        described_class.reset_column_information
        expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
      end

      let(:init) { double('block') }

      it 'calculates next internal ids on the fly' do
        val = rand(1..100)
A
Andreas Brandl 已提交
75

76 77 78 79
        expect(init).to receive(:call).with(issue).and_return(val)
        expect(subject).to eq(val + 1)
      end
    end
80 81 82 83
  end

  describe '#increment_and_save!' do
    let(:id) { create(:internal_id) }
84
    subject { id.increment_and_save! }
85 86 87

    it 'returns incremented iid' do
      value = id.last_value
A
Andreas Brandl 已提交
88

89 90 91 92 93
      expect(subject).to eq(value + 1)
    end

    it 'saves the record' do
      subject
A
Andreas Brandl 已提交
94

95 96 97 98 99 100 101 102 103 104 105 106
      expect(id.changed?).to be_falsey
    end

    context 'with last_value=nil' do
      let(:id) { build(:internal_id, last_value: nil) }

      it 'returns 1' do
        expect(subject).to eq(1)
      end
    end
  end
end