tag_spec.rb 8.5 KB

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