diff --git a/spec/factories/recent_view.rb b/spec/factories/recent_view.rb index 85742bc24..8ef6d159c 100644 --- a/spec/factories/recent_view.rb +++ b/spec/factories/recent_view.rb @@ -1,9 +1,21 @@ FactoryBot.define do factory :recent_view do - recent_view_object_id { ObjectLookup.by_name('User') } - o_id 1 - created_by_id 1 - created_at Time.zone.now - updated_at Time.zone.now + transient do + type { :ticket } + user_role { :agent } + end + + recent_view_object_id { ObjectLookup.by_name(type.to_s.camelcase) } + + # select a random record of the given object class + o_id do + type.to_s.camelcase.constantize.order('RANDOM()').first.id + end + + # assign to an existing user, if possible + created_by_id do + User.find { |u| u.role?(user_role.capitalize) }&.id || + create("#{user_role}_user").id + end end end diff --git a/spec/models/recent_view_spec.rb b/spec/models/recent_view_spec.rb new file mode 100644 index 000000000..111076b04 --- /dev/null +++ b/spec/models/recent_view_spec.rb @@ -0,0 +1,179 @@ +require 'rails_helper' + +RSpec.describe RecentView, type: :model do + let(:admin) { create(:admin_user) } + let(:agent) { create(:agent_user) } + let(:customer) { create(:customer_user) } + let(:ticket) { create(:ticket, owner: owner, customer: customer) } + let(:tickets) { create_list(:ticket, 15, owner: owner, customer: customer) } + let(:owner) { admin } + + describe '::list' do + xit 'returns a sample of recently viewed objects (e.g., tickets/users/organizations)' do + tickets.each { |t| described_class.log('Ticket', t.id, admin) } + + expect(described_class.list(admin).map(&:o_id)).to include(*tickets.last(10).map(&:id)) + end + + it 'returns up to 10 results by default' do + tickets.each { |t| described_class.log('Ticket', t.id, admin) } + + expect(described_class.list(admin).length).to eq(10) + end + + context 'with a `limit` argument (optional)' do + it 'returns up to that number of results' do + tickets.each { |t| described_class.log('Ticket', t.id, admin) } + + expect(described_class.list(admin, 12).length).to eq(12) + end + end + + context 'with an `object_name` argument (optional)' do + it 'includes only the specified model class' do + described_class.log('Ticket', ticket.id, admin) + described_class.log('Organization', 1, admin) + + expect(described_class.list(admin, 10, 'Organization').length).to eq(1) + end + + it 'does not include merged tickets in results' do + described_class.log('Ticket', ticket.id, admin) + ticket.update(state: Ticket::State.find_by(name: 'merged')) + + expect(described_class.list(admin, 10, 'Ticket').length).to eq(0) + end + + it 'does not include removed tickets in results' do + described_class.log('Ticket', ticket.id, admin) + ticket.update(state: Ticket::State.find_by(name: 'removed')) + + expect(described_class.list(admin, 10, 'Ticket').length).to eq(0) + end + end + + it 'does not include duplicate results' do + 5.times { described_class.log('Ticket', ticket.id, admin) } + + expect(described_class.list(admin).length).to eq(1) + end + + it 'does not include deleted tickets in results' do + described_class.log('Ticket', ticket.id, admin) + ticket.destroy + + expect(described_class.list(admin).length).to eq(0) + end + + describe 'access privileges' do + context 'when given user is agent' do + let(:owner) { agent } + + it 'includes own tickets in results' do + described_class.log('Ticket', ticket.id, agent) + + expect(described_class.list(agent).length).to eq(1) + end + + it 'does not include other agents’ tickets in results' do + described_class.log('Ticket', ticket.id, agent) + ticket.update(owner: User.first) + + expect(described_class.list(agent).length).to eq(0) + end + + it 'includes any organizations in results' do + agent.update(organization: nil) + described_class.log('Organization', 1, agent) + + expect(described_class.list(agent).length).to eq(1) + end + end + + context 'when given user is customer' do + it 'includes own tickets in results' do + described_class.log('Ticket', ticket.id, customer) + + expect(described_class.list(customer).length).to eq(1) + end + + it 'does not include other customers’ tickets in results' do + described_class.log('Ticket', ticket.id, customer) + ticket.update(customer: User.first) + + expect(described_class.list(customer).length).to eq(0) + end + + it 'includes own organization in results' do + customer.update(organization: Organization.first) + described_class.log('Organization', 1, customer) + + expect(described_class.list(customer).length).to eq(1) + end + + it 'does not include other organizations in results' do + customer.update(organization: Organization.first) + described_class.log('Organization', 1, customer) + customer.update(organization: nil) + + expect(described_class.list(customer).length).to eq(0) + end + end + end + end + + describe '::user_log_destroy' do + it 'deletes all RecentView records for a given user' do + create_list(:recent_view, 10, created_by_id: admin.id) + + expect { described_class.user_log_destroy(admin) } + .to change { described_class.exists?(created_by_id: admin.id) }.to(false) + end + end + + describe '::log' do + let(:viewed_object) { ticket } + let(:viewed_object_class_id) { ObjectLookup.by_name(viewed_object.class.name) } + + it 'wraps RecentView.create' do + expect do + described_class.log(viewed_object.class.name, viewed_object.id, admin) + end.to change { RecentView.count }.by(1) + end + + describe 'access privileges' do + let(:owner) { agent } + + it 'does not create RecentView for records the given user cannot read' do + ticket.update(owner: User.first, # read access may come from ticket ownership, + customer: User.first, # customer status, + organization: nil) # organization's 'shared' status, + agent.update(groups: []) # and membership in the Ticket's group + + expect do + described_class.log(viewed_object.class.name, viewed_object.id, agent) + end.not_to change { RecentView.count } + end + end + + context 'when given an invalid object' do + it 'does not create RecentView for non-existent record' do + expect do + described_class.log('User', 99_999_999, admin) + end.not_to change { RecentView.count } + end + + it 'does not create RecentView for instance of non-ObjectLookup class' do + expect do + described_class.log('Overview', 1, admin) + end.not_to change { RecentView.count } + end + + it 'does not create RecentView for instance of non-existent class' do + expect do + described_class.log('NonExistentClass', 1, admin) + end.not_to change { RecentView.count } + end + end + end +end diff --git a/test/unit/recent_view_test.rb b/test/unit/recent_view_test.rb deleted file mode 100644 index fdd6b4cb9..000000000 --- a/test/unit/recent_view_test.rb +++ /dev/null @@ -1,188 +0,0 @@ - -require 'test_helper' - -class RecentViewTest < ActiveSupport::TestCase - - test 'simple tests' do - - ticket1 = Ticket.create( - title: 'RecentViewTest 1 some title äöüß', - group: Group.lookup( name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup( name: 'new' ), - priority: Ticket::Priority.lookup( name: '2 normal' ), - updated_by_id: 1, - created_by_id: 1, - ) - assert(ticket1, 'ticket created') - ticket2 = Ticket.create( - title: 'RecentViewTest 2 some title äöüß', - group: Group.lookup(name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - updated_by_id: 1, - created_by_id: 1, - ) - assert(ticket2, 'ticket created') - user1 = User.find(2) - RecentView.user_log_destroy(user1) - - RecentView.log(ticket1.class.to_s, ticket1.id, user1) - travel 1.second - RecentView.log(ticket2.class.to_s, ticket2.id, user1) - travel 1.second - RecentView.log(ticket1.class.to_s, ticket1.id, user1) - travel 1.second - RecentView.log(ticket1.class.to_s, ticket1.id, user1) - - list = RecentView.list(user1) - assert(list[0].o_id, ticket1.id) - assert(list[0].object.name, 'Ticket') - - assert(list[1].o_id, ticket2.id) - assert(list[1].object.name, 'Ticket') - assert_equal(2, list.count) - - ticket1.destroy - ticket2.destroy - - list = RecentView.list(user1) - assert_not(list[0], 'check if recent view list is empty') - travel_back - end - - test 'existing tests' do - user = User.find(2) - - # log entry of not existing object - RecentView.user_log_destroy(user) - RecentView.log('ObjectNotExisting', 1, user) - - # check if list is empty - list = RecentView.list(user) - assert_not(list[0], 'check if recent view list is empty') - - # log entry of not existing record - RecentView.user_log_destroy(user) - RecentView.log('User', 99_999_999, user) - - # check if list is empty - list = RecentView.list(user) - assert_not(list[0], 'check if recent view list is empty') - - # log entry of not existing model with permission check - RecentView.user_log_destroy(user) - RecentView.log('Overview', 99_999_999, user) - - # check if list is empty - list = RecentView.list(user) - assert_not(list[0], 'check if recent view list is empty') - end - - test 'permission tests' do - customer = User.find(2) - - groups = Group.where(name: 'Users') - roles = Role.where(name: 'Agent') - agent = User.create_or_update( - login: 'recent-viewed-agent@example.com', - firstname: 'RecentViewed', - lastname: 'Agent', - email: 'recent-viewed-agent@example.com', - password: 'agentpw', - active: true, - roles: roles, - groups: groups, - updated_by_id: 1, - created_by_id: 1, - ) - Group.create_if_not_exists( - name: 'WithoutAccess', - note: 'Test for not access check.', - updated_by_id: 1, - created_by_id: 1 - ) - organization2 = Organization.create_if_not_exists( - name: 'Customer Organization Recent View 2', - note: 'some note', - updated_by_id: 1, - created_by_id: 1, - ) - - # no access for customer - ticket1 = Ticket.create( - title: 'RecentViewTest 1 some title äöüß', - group: Group.lookup(name: 'WithoutAccess'), - customer_id: 1, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - updated_by_id: 1, - created_by_id: 1, - ) - assert(ticket1, 'ticket created') - - # log entry of not existing object - RecentView.user_log_destroy(customer) - RecentView.log(ticket1.class.to_s, ticket1.id, customer) - - # check if list is empty - list = RecentView.list(customer) - assert_not(list[0], 'check if recent view list is empty') - - # log entry of not existing object - RecentView.user_log_destroy(agent) - RecentView.log(ticket1.class.to_s, ticket1.id, agent) - - # check if list is empty - list = RecentView.list(agent) - assert_not(list[0], 'check if recent view list is empty') - - # access for customer via customer id - ticket1 = Ticket.create( - title: 'RecentViewTest 1 some title äöüß', - group: Group.lookup(name: 'WithoutAccess'), - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - updated_by_id: 1, - created_by_id: 1, - ) - assert(ticket1, 'ticket created') - - # log entry - RecentView.user_log_destroy(customer) - RecentView.log(ticket1.class.to_s, ticket1.id, customer) - - # check if list is empty - list = RecentView.list(customer) - assert(list[0].o_id, ticket1.id) - assert(list[0].object.name, 'Ticket') - assert_not(list[1], 'check if recent view list is empty') - - # log entry - organization1 = Organization.find(1) - RecentView.user_log_destroy(customer) - RecentView.log(organization1.class.to_s, organization1.id, customer) - RecentView.log(organization2.class.to_s, organization2.id, customer) - - # check if list is empty - list = RecentView.list(customer) - assert(list[0], 'check if recent view list is empty') - assert_not(list[1], 'check if recent view list is empty') - - # log entry - organization1 = Organization.find(1) - RecentView.user_log_destroy(agent) - RecentView.log(organization1.class.to_s, organization1.id, agent) - - # check if list is empty - list = RecentView.list(agent) - assert(list[0].o_id, organization1.id) - assert(list[0].object.name, 'Organization') - assert_not(list[1], 'check if recent view list is empty') - - organization2.destroy - end - -end