2022-01-01 13:38:12 +00:00
|
|
|
|
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
2021-06-01 12:20:20 +00:00
|
|
|
|
|
2019-01-22 16:35:01 +00:00
|
|
|
|
require 'rails_helper'
|
|
|
|
|
require 'models/application_model_examples'
|
2019-01-28 06:04:05 +00:00
|
|
|
|
require 'models/concerns/can_be_imported_examples'
|
2019-01-22 16:35:01 +00:00
|
|
|
|
|
|
|
|
|
RSpec.describe History, type: :model do
|
2019-02-26 11:00:46 +00:00
|
|
|
|
it_behaves_like 'ApplicationModel', can_assets: { own_attributes: false }
|
2019-01-28 06:04:05 +00:00
|
|
|
|
it_behaves_like 'CanBeImported'
|
2019-06-28 13:07:14 +00:00
|
|
|
|
|
|
|
|
|
describe '.list' do
|
|
|
|
|
context 'when given an object with no histories' do
|
|
|
|
|
let!(:object) { create(:'cti/log') }
|
|
|
|
|
|
|
|
|
|
it 'returns an empty array' do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
expect(described_class.list(object.class.name, object.id))
|
2019-06-28 13:07:14 +00:00
|
|
|
|
.to be_an(Array).and be_empty
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when given an object with histories' do
|
|
|
|
|
context 'and called without "related_history_object" argument' do
|
|
|
|
|
let!(:object) { create(:user) }
|
|
|
|
|
|
|
|
|
|
before { object.update(email: 'foo@example.com') }
|
|
|
|
|
|
|
|
|
|
context 'or "assets" flag' do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
let(:list) { described_class.list(object.class.name, object.id) }
|
2019-06-28 13:07:14 +00:00
|
|
|
|
|
|
|
|
|
it 'returns an array of attribute hashes for those histories' do
|
|
|
|
|
expect(list).to match_array(
|
|
|
|
|
[
|
|
|
|
|
hash_including(
|
2020-06-22 09:57:45 +00:00
|
|
|
|
'o_id' => object.id,
|
2019-06-28 13:07:14 +00:00
|
|
|
|
),
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
'value_to' => 'foo@example.com',
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'replaces *_id attributes with the corresponding association #name' do
|
|
|
|
|
expect(list.first)
|
|
|
|
|
.to not_include('history_object_id', 'history_type_id')
|
|
|
|
|
.and include(
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'created',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(list.second)
|
|
|
|
|
.to not_include('history_object_id', 'history_type_id', 'history_attribute_id')
|
|
|
|
|
.and include(
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'updated',
|
|
|
|
|
'attribute' => 'email',
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'but with "assets" flag' do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
let(:list) { described_class.list(object.class.name, object.id, nil, true) }
|
2019-06-28 13:07:14 +00:00
|
|
|
|
let(:matching_histories) do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
described_class.where(
|
2019-06-28 13:07:14 +00:00
|
|
|
|
o_id: object.id,
|
|
|
|
|
history_object_id: History::Object.lookup(name: object.class.name).id
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'returns a hash including an array of history attribute hashes' do
|
|
|
|
|
expect(list).to include(
|
|
|
|
|
list: [
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'created',
|
|
|
|
|
),
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'updated',
|
|
|
|
|
'attribute' => 'email',
|
|
|
|
|
'value_to' => 'foo@example.com',
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'returns a hash including each history record’s FE assets' do
|
|
|
|
|
expect(list).to include(
|
|
|
|
|
assets: matching_histories.reduce({}) { |assets, h| h.assets(assets) }
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'with "related_history_object" argument' do
|
|
|
|
|
let!(:object) { related_object.ticket }
|
2020-06-22 09:57:45 +00:00
|
|
|
|
let!(:related_object) { create(:ticket_article, internal: true) } # MUST be internal, or else callbacks will create additional histories
|
2019-06-28 13:07:14 +00:00
|
|
|
|
|
|
|
|
|
before { object.update(title: 'Lorem ipsum dolor') }
|
|
|
|
|
|
|
|
|
|
context 'but no "assets" flag' do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
let(:list) { described_class.list(object.class.name, object.id, 'Ticket::Article') }
|
2019-06-28 13:07:14 +00:00
|
|
|
|
|
|
|
|
|
it 'returns an array of attribute hashes for those histories' do
|
|
|
|
|
expect(list).to match_array(
|
|
|
|
|
[
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
),
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => related_object.id,
|
|
|
|
|
),
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
'value_to' => 'Lorem ipsum dolor',
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'replaces *_id attributes with the corresponding association #name' do
|
|
|
|
|
expect(list.first)
|
|
|
|
|
.to not_include('history_object_id', 'history_type_id')
|
|
|
|
|
.and include(
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'created',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(list.second)
|
|
|
|
|
.to not_include('history_object_id', 'history_type_id')
|
|
|
|
|
.and include(
|
|
|
|
|
'object' => related_object.class.name,
|
|
|
|
|
'type' => 'created',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(list.third)
|
|
|
|
|
.to not_include('history_object_id', 'history_type_id', 'history_attribute_id')
|
|
|
|
|
.and include(
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'updated',
|
|
|
|
|
'attribute' => 'title',
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'and "assets" flag' do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
let(:list) { described_class.list(object.class.name, object.id, 'Ticket::Article', true) }
|
2019-06-28 13:07:14 +00:00
|
|
|
|
let(:matching_histories) do
|
2019-09-16 15:04:17 +00:00
|
|
|
|
described_class.where(
|
2019-06-28 13:07:14 +00:00
|
|
|
|
o_id: object.id,
|
|
|
|
|
history_object_id: History::Object.lookup(name: object.class.name).id
|
2019-09-16 15:04:17 +00:00
|
|
|
|
) + described_class.where(
|
2019-06-28 13:07:14 +00:00
|
|
|
|
o_id: related_object.id,
|
|
|
|
|
history_object_id: History::Object.lookup(name: related_object.class.name).id
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'returns a hash including an array of history attribute hashes' do
|
|
|
|
|
expect(list).to include(
|
|
|
|
|
list: [
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'created',
|
|
|
|
|
),
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => related_object.id,
|
|
|
|
|
'object' => related_object.class.name,
|
|
|
|
|
'type' => 'created',
|
|
|
|
|
),
|
|
|
|
|
hash_including(
|
|
|
|
|
'o_id' => object.id,
|
|
|
|
|
'object' => object.class.name,
|
|
|
|
|
'type' => 'updated',
|
|
|
|
|
'attribute' => 'title',
|
|
|
|
|
'value_to' => 'Lorem ipsum dolor',
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'returns a hash including each history record’s FE assets' do
|
|
|
|
|
expect(list).to include(
|
|
|
|
|
assets: matching_histories.reduce({}) { |assets, h| h.assets(assets) }
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2020-07-16 17:50:05 +00:00
|
|
|
|
|
|
|
|
|
shared_examples 'lookup and create if needed' do |prefix|
|
|
|
|
|
let(:prefix) { prefix }
|
|
|
|
|
let(:value_string) { Faker::Lorem.word }
|
|
|
|
|
let(:value_symbol) { value_string.to_sym }
|
|
|
|
|
let(:method_name) { "#{prefix}_lookup" }
|
|
|
|
|
let(:cache_key) { "#{described_class}::#{prefix.capitalize}::#{value_string}" }
|
|
|
|
|
|
|
|
|
|
context 'when object does not exist' do
|
|
|
|
|
it 'creates with a given String' do
|
|
|
|
|
expect(described_class.send(method_name, value_string)).to be_present
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'creates with a given Symbol' do
|
|
|
|
|
expect(described_class.send(method_name, value_symbol)).to be_present
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when object exists' do
|
|
|
|
|
before do
|
|
|
|
|
described_class.send(method_name, value_string)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'retrieves object with a given String' do
|
|
|
|
|
expect(described_class.send(method_name, value_string)).to be_present
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'hits cache with a given String' do
|
|
|
|
|
allow(Rails.cache).to receive(:read)
|
|
|
|
|
described_class.send(method_name, value_string)
|
|
|
|
|
expect(Rails.cache).to have_received(:read).with(cache_key)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'retrieves object with a given Symbol' do
|
|
|
|
|
expect(described_class.send(method_name, value_symbol)).to be_present
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'hits cache with a given Symbol' do
|
|
|
|
|
allow(Rails.cache).to receive(:read)
|
|
|
|
|
described_class.send(method_name, value_symbol)
|
|
|
|
|
expect(Rails.cache).to have_received(:read).with(cache_key)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# https://github.com/zammad/zammad/issues/3121
|
|
|
|
|
describe '.type_lookup' do
|
|
|
|
|
include_examples 'lookup and create if needed', 'type'
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# https://github.com/zammad/zammad/issues/3121
|
|
|
|
|
describe '.object_lookup' do
|
|
|
|
|
include_examples 'lookup and create if needed', 'object'
|
|
|
|
|
end
|
2019-01-22 16:35:01 +00:00
|
|
|
|
end
|