222 lines
6.6 KiB
Ruby
222 lines
6.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
|
|
describe '.has_internal_id' do
|
|
describe 'Module inclusion' do
|
|
subject { described_class }
|
|
|
|
it { is_expected.to include_module(AtomicInternalId) }
|
|
end
|
|
|
|
describe 'Validation' do
|
|
context 'when presence validation is required' do
|
|
before do
|
|
skip unless validate_presence
|
|
end
|
|
|
|
context 'when creating an object' do
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
|
|
end
|
|
|
|
it 'raises an error if the internal id is blank' do
|
|
expect { instance.save! }.to raise_error(AtomicInternalId::MissingValueError)
|
|
end
|
|
end
|
|
|
|
context 'when updating an object' do
|
|
it 'raises an error if the internal id is blank' do
|
|
instance.save!
|
|
|
|
write_internal_id(nil)
|
|
allow(instance).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
|
|
|
|
expect { instance.save! }.to raise_error(AtomicInternalId::MissingValueError)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when presence validation is not required' do
|
|
before do
|
|
skip if validate_presence
|
|
end
|
|
|
|
context 'when creating an object' do
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
|
|
end
|
|
|
|
it 'does not raise an error if the internal id is blank' do
|
|
expect(read_internal_id).to be_nil
|
|
|
|
expect { instance.save! }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
context 'when updating an object' do
|
|
it 'does not raise an error if the internal id is blank' do
|
|
instance.save!
|
|
|
|
write_internal_id(nil)
|
|
allow(instance).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
|
|
|
|
expect { instance.save! }.not_to raise_error
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Creating an instance' do
|
|
subject { instance.save! }
|
|
|
|
it 'saves a new instance properly' do
|
|
expect { subject }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
describe 'internal id generation' do
|
|
subject { instance.save! }
|
|
|
|
it 'calls InternalId.generate_next and sets internal id attribute' do
|
|
iid = rand(1..1000)
|
|
|
|
# Need to do this before evaluating instance otherwise it gets set
|
|
# already in factory
|
|
allow(InternalId).to receive(:generate_next).and_return(iid)
|
|
|
|
subject
|
|
expect(read_internal_id).to eq(iid)
|
|
|
|
expect(InternalId).to have_received(:generate_next).with(instance, scope_attrs, usage, any_args)
|
|
end
|
|
|
|
it 'does not overwrite an existing internal id' do
|
|
write_internal_id(4711)
|
|
|
|
allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
|
|
expect { subject }.not_to change { read_internal_id }
|
|
end
|
|
end
|
|
|
|
context 'when the instance has an internal ID set' do
|
|
let(:internal_id) { 9001 }
|
|
|
|
it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do
|
|
write_internal_id(internal_id)
|
|
|
|
expect(InternalId)
|
|
.to receive(:track_greatest)
|
|
.with(instance, scope_attrs, usage, internal_id, any_args)
|
|
.and_return(internal_id)
|
|
|
|
subject
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'unsetting the instance internal id on rollback' do
|
|
context 'when the internal id has been changed' do
|
|
context 'when the internal id is automatically set' do
|
|
it 'clears it on the instance' do
|
|
write_internal_id(nil)
|
|
|
|
allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
|
|
expect_iid_to_be_set_and_rollback
|
|
end
|
|
|
|
expect(read_internal_id).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when the internal id is manually set' do
|
|
it 'does not clear it on the instance' do
|
|
write_internal_id(100)
|
|
|
|
allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
|
|
expect_iid_to_be_set_and_rollback
|
|
end
|
|
|
|
expect(read_internal_id).not_to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when the internal id has not been changed' do
|
|
it 'preserves the value on the instance' do
|
|
instance.save!
|
|
original_id = read_internal_id
|
|
|
|
expect(original_id).not_to be_nil
|
|
|
|
expect_iid_to_be_set_and_rollback
|
|
|
|
expect(read_internal_id).to eq(original_id)
|
|
end
|
|
end
|
|
|
|
def expect_iid_to_be_set_and_rollback
|
|
instance.transaction(requires_new: true) do
|
|
instance.save!
|
|
|
|
expect(read_internal_id).not_to be_nil
|
|
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'supply of internal ids' do
|
|
let(:scope_value) { scope_attrs.each_value.first }
|
|
let(:method_name) { :"with_#{scope}_#{internal_id_attribute}_supply" }
|
|
|
|
it 'provides a persistent supply of IID values, sensitive to the current state' do
|
|
iid = rand(1..1000)
|
|
write_internal_id(iid)
|
|
instance.public_send(:"track_#{scope}_#{internal_id_attribute}!")
|
|
|
|
# Allocate 3 IID values
|
|
described_class.public_send(method_name, scope_value) do |supply|
|
|
3.times { supply.next_value }
|
|
end
|
|
|
|
described_class.public_send(method_name, scope_value) do |supply|
|
|
expect(supply.next_value).to eq(iid + 4)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#reset_scope_internal_id_attribute" do
|
|
it 'rewinds the allocated IID' do
|
|
expect { ensure_scope_attribute! }.not_to raise_error
|
|
expect(read_internal_id).not_to be_nil
|
|
|
|
expect(reset_scope_attribute).to be_nil
|
|
expect(read_internal_id).to be_nil
|
|
end
|
|
|
|
it 'allocates the same IID' do
|
|
internal_id = ensure_scope_attribute!
|
|
reset_scope_attribute
|
|
expect(read_internal_id).to be_nil
|
|
|
|
expect(ensure_scope_attribute!).to eq(internal_id)
|
|
end
|
|
end
|
|
|
|
def ensure_scope_attribute!
|
|
instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!")
|
|
end
|
|
|
|
def reset_scope_attribute
|
|
instance.public_send(:"reset_#{scope}_#{internal_id_attribute}")
|
|
end
|
|
|
|
def read_internal_id
|
|
instance.public_send(internal_id_attribute)
|
|
end
|
|
|
|
def write_internal_id(value)
|
|
instance.public_send(:"#{internal_id_attribute}=", value)
|
|
end
|
|
end
|
|
end
|