tag_spec.rb 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Tag, type: :model do
  4. subject(:tag) { create(:tag) }
  5. describe '.tag_add' do
  6. it 'touches the target object' do
  7. expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: Ticket.first.id, created_by_id: 1) }
  8. .to change { Ticket.first.updated_at }
  9. end
  10. context 'when a Tag::Object does not exist for the given class' do
  11. it 'creates it and assigns it to a new Tag' do
  12. expect { described_class.tag_add(object: 'Foo', item: 'bar', o_id: 1, created_by_id: 1) }
  13. .to change(described_class, :count).by(1)
  14. .and change { Tag::Object.exists?(name: 'Foo') }.to(true)
  15. .and change { described_class.last&.tag_object&.name }.to('Foo')
  16. end
  17. end
  18. context 'when a Tag::Object already exists for the given class' do
  19. let!(:tag_object) { Tag::Object.find_or_create_by(name: 'Ticket') }
  20. it 'assigns it to a new Tag' do
  21. expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: 1, created_by_id: 1) }
  22. .to change(described_class, :count).by(1)
  23. .and not_change(Tag::Object, :count)
  24. .and change { described_class.last&.tag_object&.name }.to('Ticket')
  25. end
  26. end
  27. context 'when a Tag::Item does not exist with the given name' do
  28. it 'creates it and assigns it to a new Tag' do
  29. expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: 1, created_by_id: 1) }
  30. .to change(described_class, :count).by(1)
  31. .and change { Tag::Item.exists?(name: 'foo') }.to(true)
  32. .and change { described_class.last&.tag_item&.name }.to('foo')
  33. end
  34. it 'strips trailing/leading whitespace' do
  35. expect { described_class.tag_add(object: 'Ticket', item: ' foo ', o_id: 1, created_by_id: 1) }
  36. .to change(described_class, :count).by(1)
  37. .and change { Tag::Item.exists?(name: 'foo') }.to(true)
  38. .and change { described_class.last&.tag_item&.name }.to('foo')
  39. end
  40. context 'and the name contains 8-bit Unicode characters' do
  41. it 'creates it and assigns it to a new Tag' do
  42. expect { described_class.tag_add(object: 'Ticket', item: 'fooöäüß', o_id: 1, created_by_id: 1) }
  43. .to change(described_class, :count).by(1)
  44. .and change { Tag::Item.exists?(name: 'fooöäüß') }.to(true)
  45. .and change { described_class.last&.tag_item&.name }.to('fooöäüß')
  46. end
  47. end
  48. context 'but the name is a case-sensitive variant of an existing Tag::Item' do
  49. let!(:tag_item) { create(:'tag/item', name: 'foo') }
  50. it 'creates it and assigns it to a new Tag' do
  51. expect { described_class.tag_add(object: 'Ticket', item: 'FOO', o_id: 1, created_by_id: 1) }
  52. .to change(described_class, :count).by(1)
  53. .and change { Tag::Item.pluck(:name).include?('FOO') }.to(true) # .exists?(name: 'FOO') fails on MySQL
  54. .and change { described_class.last&.tag_item&.name }.to('FOO')
  55. end
  56. end
  57. end
  58. context 'when a Tag::Item already exists with the given name' do
  59. let!(:tag_item) { create(:'tag/item', name: 'foo') }
  60. it 'assigns it to a new Tag' do
  61. expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: 1, created_by_id: 1) }
  62. .to change(described_class, :count).by(1)
  63. .and not_change(Tag::Item, :count)
  64. .and change { described_class.last&.tag_item&.name }.to('foo')
  65. end
  66. it 'strips leading/trailing whitespace' do
  67. expect { described_class.tag_add(object: 'Ticket', item: ' foo ', o_id: 1, created_by_id: 1) }
  68. .to change(described_class, :count).by(1)
  69. .and not_change(Tag::Item, :count)
  70. .and change { described_class.last&.tag_item&.name }.to('foo')
  71. end
  72. end
  73. context 'when a Tag already exists for the specified record with the given name' do
  74. let!(:tag) { create(:tag, o: Ticket.first, tag_item: tag_item) }
  75. let(:tag_item) { create(:'tag/item', name: 'foo') }
  76. it 'does not create any records' do
  77. expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: Ticket.first.id, created_by_id: 1) }
  78. .to not_change(described_class, :count)
  79. .and not_change(Tag::Item, :count)
  80. end
  81. end
  82. end
  83. describe '.tag_remove' do
  84. it 'touches the target object' do
  85. expect { described_class.tag_remove(object: 'Ticket', item: 'foo', o_id: Ticket.first.id, created_by_id: 1) }
  86. .to change { Ticket.first.updated_at }
  87. end
  88. context 'when a matching Tag exists' do
  89. let!(:tag) { create(:tag, o: Ticket.first, tag_item: tag_item) }
  90. let(:tag_item) { create(:'tag/item', name: 'foo') }
  91. it 'destroys the Tag' do
  92. expect { described_class.tag_remove(object: 'Ticket', o_id: Ticket.first.id, item: 'foo') }
  93. .to change(described_class, :count).by(-1)
  94. end
  95. end
  96. context 'when no matching Tag exists' do
  97. it 'makes no changes' do
  98. expect { described_class.tag_remove(object: 'Ticket', o_id: Ticket.first.id, item: 'foo') }
  99. .not_to change(described_class, :count)
  100. end
  101. end
  102. end
  103. describe '.tag_list' do
  104. context 'with ASCII item names' do
  105. before { items.map { |i| create(:tag, tag_item: i, o: Ticket.first) } }
  106. let(:items) do
  107. [
  108. create(:'tag/item', name: 'foo'),
  109. create(:'tag/item', name: 'bar'),
  110. create(:'tag/item', name: 'BAR'),
  111. ]
  112. end
  113. it 'returns all tag names (case-sensitive) for a given record' do
  114. expect(described_class.tag_list(object: 'Ticket', o_id: Ticket.first.id))
  115. .to match_array(%w[foo bar BAR])
  116. end
  117. end
  118. context 'with Unicode (8-bit) item names' do
  119. before { items.map { |i| create(:tag, tag_item: i, o: Ticket.first) } }
  120. let(:items) do
  121. [
  122. create(:'tag/item', name: 'fooöäüß'),
  123. create(:'tag/item', name: 'baröäüß'),
  124. create(:'tag/item', name: 'BARöäüß'),
  125. ]
  126. end
  127. it 'returns all tag names (case-sensitive) for a given record' do
  128. expect(described_class.tag_list(object: 'Ticket', o_id: Ticket.first.id))
  129. .to match_array(%w[fooöäüß baröäüß BARöäüß])
  130. end
  131. end
  132. end
  133. describe '.tag_references' do
  134. context 'with objects with a tag', current_user_id: 1 do
  135. before do
  136. [object_1, object_2].each do |elem|
  137. described_class.tag_add object: elem.class.name, o_id: elem.id, item: tag
  138. end
  139. end
  140. let(:object_1) { create(:ticket) }
  141. let(:object_2) { create(:knowledge_base_answer) }
  142. let(:tag) { 'foo' }
  143. it 'returns references' do
  144. expect(described_class.tag_references(tag: tag)).to match_array [
  145. [object_1.class.name, object_1.id],
  146. [object_2.class.name, object_2.id]
  147. ]
  148. end
  149. it 'returns references for the given object type' do
  150. expect(described_class.tag_references(tag: tag, object: 'Ticket')).to match_array [
  151. [object_1.class.name, object_1.id]
  152. ]
  153. end
  154. end
  155. end
  156. end