Browse Source

Refactoring: Migrate tag_test to RSpec

Ryan Lue 5 years ago
parent
commit
52f5b33484

+ 4 - 14
app/models/tag.rb

@@ -145,20 +145,10 @@ returns
 =end
 
   def self.tag_list(data)
-    tag_object_id_requested = Tag::Object.lookup(name: data[:object])
-    return [] if !tag_object_id_requested
-
-    tag_search = Tag.where(
-      tag_object_id: tag_object_id_requested,
-      o_id:          data[:o_id],
-    ).order(:id)
-
-    tag_search.each_with_object([]) do |tag, result|
-      tag_item = Tag::Item.lookup(id: tag.tag_item_id)
-      next if !tag_item
-
-      result.push tag_item.name
-    end
+    Tag.joins(:tag_item, :tag_object)
+       .where(o_id: data[:o_id], tag_objects: { name: data[:object] })
+       .order(:id)
+       .pluck('tag_items.name')
   end
 
   class Object < ApplicationModel

+ 5 - 0
spec/factories/tag/item.rb

@@ -0,0 +1,5 @@
+FactoryBot.define do
+  factory :'tag/item', aliases: %i[tag_item] do
+    sequence(:name) { |n| "Item #{n}" }
+  end
+end

+ 66 - 0
spec/models/concerns/has_tags_examples.rb

@@ -0,0 +1,66 @@
+RSpec.shared_examples 'HasTags' do
+  subject { create(described_class.name.underscore) }
+
+  describe '#tag_add' do
+    let(:item_name) { 'foo' }
+
+    it 'delegates to Tag.tag_add' do
+      expect(Tag)
+        .to receive(:tag_add)
+        .with(object:        described_class.name,
+              o_id:          subject.id,
+              item:          item_name,
+              created_by_id: nil)
+
+      subject.tag_add(item_name)
+    end
+
+    it 'optionally accepts a current_user_id argument' do
+      expect(Tag)
+        .to receive(:tag_add)
+        .with(object:        described_class.name,
+              o_id:          subject.id,
+              item:          item_name,
+              created_by_id: 1)
+
+      subject.tag_add(item_name, 1)
+    end
+  end
+
+  describe '#tag_remove' do
+    let(:item_name) { 'foo' }
+
+    it 'delegates to Tag.tag_remove' do
+      expect(Tag)
+        .to receive(:tag_remove)
+        .with(object:        described_class.name,
+              o_id:          subject.id,
+              item:          item_name,
+              created_by_id: nil)
+
+      subject.tag_remove(item_name)
+    end
+
+    it 'optionally accepts a current_user_id argument' do
+      expect(Tag)
+        .to receive(:tag_remove)
+        .with(object:        described_class.name,
+              o_id:          subject.id,
+              item:          item_name,
+              created_by_id: 1)
+
+      subject.tag_remove(item_name, 1)
+    end
+  end
+
+  describe '#tag_list' do
+    it 'delegates to Tag.tag_list' do
+      expect(Tag)
+        .to receive(:tag_list)
+        .with(object: described_class.name,
+              o_id:   subject.id)
+
+      subject.tag_list
+    end
+  end
+end

+ 125 - 0
spec/models/tag/item_spec.rb

@@ -0,0 +1,125 @@
+require 'rails_helper'
+
+RSpec.describe Tag::Item do
+  subject(:item) { create(:'tag/item') }
+
+  describe '.rename' do
+    context 'when given a unique item name' do
+      it 'updates the name on the Tag::Item' do
+        expect { described_class.rename(id: item.id, name: 'foo') }
+          .to change { item.reload.name }.to('foo')
+      end
+
+      it 'strips trailing/leading whitespace' do
+        expect { described_class.rename(id: item.id, name: '  foo ') }
+          .to change { item.reload.name }.to('foo')
+      end
+    end
+
+    context 'when given a conflicting/existing item name' do
+      let!(:item_2) { create(:'tag/item', name: 'foo') }
+
+      context 'with no redundant tags' do
+        let!(:tag) { create(:tag, o: Ticket.first, tag_item: item) }
+
+        it 'reassigns all tags from old-name item to new-name item' do
+          expect { described_class.rename(id: item.id, name: item_2.name) }
+            .to change { tag.reload.tag_item }.to(item_2)
+            .and change { Ticket.first.tag_list }.to([item_2.name])
+        end
+
+        it 'strips trailing/leading whitespace' do
+          expect { described_class.rename(id: item.id, name: "  #{item_2.name} ") }
+            .to change { tag.reload.tag_item }.to(item_2)
+            .and change { Ticket.first.tag_list }.to([item_2.name])
+        end
+      end
+
+      context 'with redundant tags' do
+        let!(:tags) do
+          [create(:tag, o: Ticket.first, tag_item: item),
+           create(:tag, o: Ticket.first, tag_item: item_2)]
+        end
+
+        it 'removes the tag assigned to old-name item' do
+          expect { described_class.rename(id: item.id, name: item_2.name) }
+            .to change { Tag.exists?(id: tags.first.id) }.to(false)
+            .and change { Ticket.first.tag_list }.to([item_2.name])
+        end
+
+        it 'strips trailing/leading whitespace' do
+          expect { described_class.rename(id: item.id, name: "  #{item_2.name} ") }
+            .to change { Tag.exists?(id: tags.first.id) }.to(false)
+            .and change { Ticket.first.tag_list }.to([item_2.name])
+        end
+      end
+
+      it 'deletes the original item' do
+        expect { described_class.rename(id: item.id, name: item_2.name) }
+          .to change { described_class.exists?(name: item.name) }.to(false)
+      end
+    end
+
+    shared_examples 'updating references to tag names' do |object_klass:, method:, label: 'ticket.tags'|
+      subject(:item) { create(:'tag/item', name: 'test1') }
+
+      context "with reference to renamed tag in its #{method} hash (contains-one)" do
+        let(:object) { create(object_klass.name.underscore, method => { label => tag_matcher }) }
+        let(:tag_matcher) { { operator: 'contains one', value: 'test1' } }
+
+        it 'updates reference with new tag name' do
+          expect { described_class.rename(id: item.id, name: 'test1_renamed') }
+            .to change { object.reload.send(method)[label][:value] }
+            .from('test1').to('test1_renamed')
+        end
+      end
+
+      context "with reference to renamed tag in its #{method} hash (contains-all)" do
+        let(:object) { create(object_klass.name.underscore, method => { label => tag_matcher }) }
+        let(:tag_matcher) { { operator: 'contains all', value: 'test1, test2, test3' } }
+
+        it 'updates reference with new tag name' do
+          expect { described_class.rename(id: item.id, name: 'test1_renamed') }
+            .to change { object.reload.send(method)[label][:value] }
+            .from('test1, test2, test3').to('test1_renamed, test2, test3')
+        end
+      end
+    end
+
+    context 'for Overview object' do
+      include_examples 'updating references to tag names', object_klass: Overview, method: :condition
+    end
+
+    context 'for Trigger object' do
+      include_examples 'updating references to tag names', object_klass: Trigger, method: :condition
+      include_examples 'updating references to tag names', object_klass: Trigger, method: :perform
+    end
+
+    context 'for scheduler (Job) object' do
+      include_examples 'updating references to tag names', object_klass: Job, method: :condition
+      include_examples 'updating references to tag names', object_klass: Job, method: :perform
+    end
+
+    context 'for PostmasterFilter object' do
+      include_examples 'updating references to tag names', object_klass: PostmasterFilter, method: :perform, label: 'x-zammad-ticket-tags'
+    end
+  end
+
+  describe '.remove' do
+    let!(:tags) do
+      [create(:tag, tag_item: item, o: User.first),
+       create(:tag, tag_item: item, o: Ticket.first)]
+    end
+
+    it 'removes the specified Tag::Item' do
+      expect { described_class.remove(item.id) }
+        .to change { described_class.exists?(id: item.id) }.to(false)
+    end
+
+    it 'removes all associated Tags' do
+      expect { described_class.remove(item.id) }
+        .to change { Tag.exists?(id: tags.first.id) }.to(false)
+        .and change { Tag.exists?(id: tags.second.id) }.to(false)
+    end
+  end
+end

+ 128 - 61
spec/models/tag_spec.rb

@@ -1,90 +1,157 @@
 require 'rails_helper'
 
-RSpec.describe Tag do
+RSpec.describe Tag, type: :model do
+  subject(:tag) { create(:tag) }
 
-  context 'rename' do
-    before do
-      Tag::Item.lookup_by_name_and_create('test1')
+  describe '.tag_add' do
+    it 'touches the target object' do
+      expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: Ticket.first.id, created_by_id: 1) }
+        .to change { Ticket.first.updated_at }
     end
 
-    def tag_rename
-      Tag::Item.rename(
-        id:            Tag::Item.lookup(name: 'test1').id,
-        name:          'test1_renamed',
-        updated_by_id: 1,
-      )
+    context 'when a Tag::Object does not exist for the given class' do
+      it 'creates it and assigns it to a new Tag' do
+        expect { described_class.tag_add(object: 'Foo', item: 'bar', o_id: 1, created_by_id: 1) }
+          .to change(Tag, :count).by(1)
+          .and change { Tag::Object.exists?(name: 'Foo') }.to(true)
+          .and change { Tag.last&.tag_object&.name }.to('Foo')
+      end
     end
 
-    it 'overview conditions with a single tag' do
-      object = create :overview, condition: { 'ticket.tags' => { operator: 'contains one', value: 'test1' } }
-      tag_rename
-      expect(Overview.find(object.id).condition['ticket.tags'][:value]).to eq('test1_renamed')
-    end
+    context 'when a Tag::Object already exists for the given class' do
+      let!(:tag_object) { Tag::Object.find_or_create_by(name: 'Ticket') }
 
-    it 'overview conditions with a tag list ' do
-      object = create :overview, condition: { 'ticket.tags' => { operator: 'contains all', value: 'test1, test2, test3' } }
-      tag_rename
-      expect(Overview.find(object.id).condition['ticket.tags'][:value]).to eq('test1_renamed, test2, test3')
+      it 'assigns it to a new Tag' do
+        expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: 1, created_by_id: 1) }
+          .to change(Tag, :count).by(1)
+          .and not_change(Tag::Object, :count)
+          .and change { Tag.last&.tag_object&.name }.to('Ticket')
+      end
     end
 
-    it 'trigger conditions with a single tag' do
-      object = create :trigger, condition: { 'ticket.tags' => { operator: 'contains one', value: 'test1' } }
-      tag_rename
-      expect(Trigger.find(object.id).condition['ticket.tags'][:value]).to eq('test1_renamed')
-    end
+    context 'when a Tag::Item does not exist with the given name' do
+      it 'creates it and assigns it to a new Tag' do
+        expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: 1, created_by_id: 1) }
+          .to change(Tag, :count).by(1)
+          .and change { Tag::Item.exists?(name: 'foo') }.to(true)
+          .and change { Tag.last&.tag_item&.name }.to('foo')
+      end
 
-    it 'trigger conditions with a tag list ' do
-      object = create :trigger, condition: { 'ticket.tags' => { operator: 'contains all', value: 'test1, test2, test3' } }
-      tag_rename
-      expect(Trigger.find(object.id).condition['ticket.tags'][:value]).to eq('test1_renamed, test2, test3')
-    end
+      it 'strips trailing/leading whitespace' do
+        expect { described_class.tag_add(object: 'Ticket', item: '  foo ', o_id: 1, created_by_id: 1) }
+          .to change(Tag, :count).by(1)
+          .and change { Tag::Item.exists?(name: 'foo') }.to(true)
+          .and change { Tag.last&.tag_item&.name }.to('foo')
+      end
+
+      context 'and the name contains 8-bit Unicode characters' do
+        it 'creates it and assigns it to a new Tag' do
+          expect { described_class.tag_add(object: 'Ticket', item: 'fooöäüß', o_id: 1, created_by_id: 1) }
+            .to change(Tag, :count).by(1)
+            .and change { Tag::Item.exists?(name: 'fooöäüß') }.to(true)
+            .and change { Tag.last&.tag_item&.name }.to('fooöäüß')
+        end
+      end
 
-    it 'trigger performs with a single tag' do
-      object = create :trigger, perform: { 'ticket.tags' => { operator: 'contains one', value: 'test1' } }
-      tag_rename
-      expect(Trigger.find(object.id).perform['ticket.tags'][:value]).to eq('test1_renamed')
+      context 'but the name is a case-sensitive variant of an existing Tag::Item' do
+        let!(:tag_item) { create(:'tag/item', name: 'foo') }
+
+        it 'creates it and assigns it to a new Tag' do
+          expect { described_class.tag_add(object: 'Ticket', item: 'FOO', o_id: 1, created_by_id: 1) }
+            .to change(Tag, :count).by(1)
+            .and change { Tag::Item.pluck(:name).include?('FOO') }.to(true) # .exists?(name: 'FOO') fails on MySQL
+            .and change { Tag.last&.tag_item&.name }.to('FOO')
+        end
+      end
     end
 
-    it 'trigger performs with a tag list ' do
-      object = create :trigger, perform: { 'ticket.tags' => { operator: 'contains all', value: 'test1, test2, test3' } }
-      tag_rename
-      expect(Trigger.find(object.id).perform['ticket.tags'][:value]).to eq('test1_renamed, test2, test3')
+    context 'when a Tag::Item already exists with the given name' do
+      let!(:tag_item) { create(:'tag/item', name: 'foo') }
+
+      it 'assigns it to a new Tag' do
+        expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: 1, created_by_id: 1) }
+          .to change(Tag, :count).by(1)
+          .and not_change(Tag::Item, :count)
+          .and change { Tag.last&.tag_item&.name }.to('foo')
+      end
+
+      it 'strips leading/trailing whitespace' do
+        expect { described_class.tag_add(object: 'Ticket', item: '  foo ', o_id: 1, created_by_id: 1) }
+          .to change(Tag, :count).by(1)
+          .and not_change(Tag::Item, :count)
+          .and change { Tag.last&.tag_item&.name }.to('foo')
+      end
     end
 
-    it 'scheduler conditions with a single tag' do
-      object = create :job, condition: { 'ticket.tags' => { operator: 'contains one', value: 'test1' } }
-      tag_rename
-      expect(Job.find(object.id).condition['ticket.tags'][:value]).to eq('test1_renamed')
+    context 'when a Tag already exists for the specified record with the given name' do
+      let!(:tag) { create(:tag, o: Ticket.first, tag_item: tag_item) }
+      let(:tag_item) { create(:'tag/item', name: 'foo') }
+
+      it 'does not create any records' do
+        expect { described_class.tag_add(object: 'Ticket', item: 'foo', o_id: Ticket.first.id, created_by_id: 1) }
+          .to not_change(Tag, :count)
+          .and not_change(Tag::Item, :count)
+      end
     end
+  end
 
-    it 'scheduler conditions with a tag list ' do
-      object = create :job, condition: { 'ticket.tags' => { operator: 'contains all', value: 'test1, test2, test3' } }
-      tag_rename
-      expect(Job.find(object.id).condition['ticket.tags'][:value]).to eq('test1_renamed, test2, test3')
+  describe '.tag_remove' do
+    it 'touches the target object' do
+      expect { described_class.tag_remove(object: 'Ticket', item: 'foo', o_id: Ticket.first.id, created_by_id: 1) }
+        .to change { Ticket.first.updated_at }
     end
 
-    it 'scheduler performs with a single tag' do
-      object = create :job, perform: { 'ticket.tags' => { operator: 'contains one', value: 'test1' } }
-      tag_rename
-      expect(Job.find(object.id).perform['ticket.tags'][:value]).to eq('test1_renamed')
+    context 'when a matching Tag exists' do
+      let!(:tag) { create(:tag, o: Ticket.first, tag_item: tag_item) }
+      let(:tag_item) { create(:'tag/item', name: 'foo') }
+
+      it 'destroys the Tag' do
+        expect { described_class.tag_remove(object: 'Ticket', o_id: Ticket.first.id, item: 'foo') }
+          .to change(Tag, :count).by(-1)
+      end
     end
 
-    it 'scheduler performs with a tag list ' do
-      object = create :job, perform: { 'ticket.tags' => { operator: 'contains all', value: 'test1, test2, test3' } }
-      tag_rename
-      expect(Job.find(object.id).perform['ticket.tags'][:value]).to eq('test1_renamed, test2, test3')
+    context 'when no matching Tag exists' do
+      it 'makes no changes' do
+        expect { described_class.tag_remove(object: 'Ticket', o_id: Ticket.first.id, item: 'foo') }
+          .not_to change(Tag, :count)
+      end
     end
+  end
+
+  describe '.tag_list' do
+    context 'with ASCII item names' do
+      before { items.map { |i| create(:tag, tag_item: i, o: Ticket.first) } }
 
-    it 'PostmasterFilter performs with a single tag' do
-      object = create :postmaster_filter, perform: { 'x-zammad-ticket-tags' => { operator: 'contains one', value: 'test1' } }
-      tag_rename
-      expect(PostmasterFilter.find(object.id).perform['x-zammad-ticket-tags'][:value]).to eq('test1_renamed')
+      let(:items) do
+        [
+          create(:'tag/item', name: 'foo'),
+          create(:'tag/item', name: 'bar'),
+          create(:'tag/item', name: 'BAR'),
+        ]
+      end
+
+      it 'returns all tag names (case-sensitive) for a given record' do
+        expect(described_class.tag_list(object: 'Ticket', o_id: Ticket.first.id))
+          .to match_array(%w[foo bar BAR])
+      end
     end
 
-    it 'PostmasterFilter performs with a tag list ' do
-      object = create :postmaster_filter, perform: { 'x-zammad-ticket-tags' => { operator: 'contains all', value: 'test1, test2, test3' } }
-      tag_rename
-      expect(PostmasterFilter.find(object.id).perform['x-zammad-ticket-tags'][:value]).to eq('test1_renamed, test2, test3')
+    context 'with Unicode (8-bit) item names' do
+      before { items.map { |i| create(:tag, tag_item: i, o: Ticket.first) } }
+
+      let(:items) do
+        [
+          create(:'tag/item', name: 'fooöäüß'),
+          create(:'tag/item', name: 'baröäüß'),
+          create(:'tag/item', name: 'BARöäüß'),
+        ]
+      end
+
+      it 'returns all tag names (case-sensitive) for a given record' do
+        expect(described_class.tag_list(object: 'Ticket', o_id: Ticket.first.id))
+          .to match_array(%w[fooöäüß baröäüß BARöäüß])
+      end
     end
   end
 end

+ 2 - 0
spec/models/ticket_spec.rb

@@ -3,6 +3,7 @@ require 'models/application_model_examples'
 require 'models/concerns/can_be_imported_examples'
 require 'models/concerns/can_lookup_examples'
 require 'models/concerns/has_history_examples'
+require 'models/concerns/has_tags_examples'
 require 'models/concerns/has_xss_sanitized_note_examples'
 require 'models/concerns/has_object_manager_attributes_validation_examples'
 
@@ -11,6 +12,7 @@ RSpec.describe Ticket, type: :model do
   it_behaves_like 'CanBeImported'
   it_behaves_like 'CanLookup'
   it_behaves_like 'HasHistory', history_relation_object: 'Ticket::Article'
+  it_behaves_like 'HasTags'
   it_behaves_like 'HasXssSanitizedNote', model_factory: :ticket
   it_behaves_like 'HasObjectManagerAttributesValidation'
 

+ 0 - 388
test/unit/tag_test.rb

@@ -1,388 +0,0 @@
-require 'test_helper'
-
-class TagTest < ActiveSupport::TestCase
-  test 'tags' do
-    tests = [
-
-      # test 1
-      {
-        tag_add: {
-          item:          'tag1',
-          object:        'Object1',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:  {
-          object: 'Object1',
-          items:  {
-            'tag1' => true,
-            'tag2' => false,
-          },
-        },
-      },
-
-      # test 2
-      {
-        tag_add: {
-          item:          'tag2',
-          object:        'Object1',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:  {
-          object: 'Object1',
-          items:  {
-            'tag1' => true,
-            'tag2' => true,
-          },
-        },
-      },
-
-      {
-        tag_add: {
-          item:          'TAG2',
-          object:        'Object1',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:  {
-          object: 'Object1',
-          items:  {
-            'tag1' => true,
-            'tag2' => true,
-            'TAG2' => true,
-          },
-        },
-      },
-
-      # test 2
-      {
-        tag_add: {
-          item:          'tagöäüß1',
-          object:        'Object2',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:  {
-          object: 'Object2',
-          items:  {
-            'tagöäüß1' => true,
-            'tag2'     => false,
-          },
-        },
-      },
-
-      # test 4
-      {
-        tag_add: {
-          item:          'Tagöäüß2',
-          object:        'Object2',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:  {
-          object: 'Object2',
-          items:  {
-            'tagöäüß1' => true,
-            'Tagöäüß2' => true,
-            'tagöäüß3' => false,
-          },
-        },
-      },
-
-      # test 5
-      {
-        tag_remove: {
-          item:          'tag1',
-          object:        'Object1',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:     {
-          object: 'Object1',
-          items:  {
-            'tag1' => false,
-            'tag2' => true,
-            'TAG2' => true,
-          },
-        },
-      },
-
-      # test 5
-      {
-        tag_remove: {
-          item:          'TAG2',
-          object:        'Object1',
-          o_id:          123,
-          created_by_id: 1
-        },
-        verify:     {
-          object: 'Object1',
-          items:  {
-            'tag1' => false,
-            'tag2' => true,
-            'TAG2' => false,
-          },
-        },
-      },
-
-    ]
-    tests.each do |test|
-      tags = nil
-      if test[:tag_add]
-        tags    = test[:tag_add]
-        success = Tag.tag_add(tags)
-        assert(success, 'Tag.tag_add successful')
-      else
-        tags    = test[:tag_remove]
-        success = Tag.tag_remove(tags)
-        assert(success, 'Tag.tag_remove successful')
-      end
-      list = Tag.tag_list(tags)
-      test[:verify][:items].each do |key, value|
-        if value == true
-          assert(list.include?(key), "Tag verify - should exists but exists #{key}")
-        else
-          assert_not(list.include?(key), "Tag verify - exists but should not #{key}")
-        end
-      end
-    end
-
-    # delete tags
-    tests.each do |test|
-      tags = test[:tag_add] || test[:tag_remove]
-      success = Tag.tag_remove(tags)
-      assert(success, 'Tag.tag_remove successful')
-      list = Tag.tag_list(tags)
-      assert_not(list.include?(tags[:item]), 'Tag entry destroyed')
-    end
-  end
-
-  test 'tags - real live' do
-
-    ticket1 = Ticket.create(
-      title:         'some title tag1',
-      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,
-    )
-    ticket2 = Ticket.create(
-      title:         'some title tag2',
-      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,
-    )
-    travel 2.seconds
-
-    ticket1.tag_add('some tag1', 1)
-    ticket1.tag_add('some tag2 ', 1)
-    ticket1.tag_add(' some tag3', 1)
-    ticket1.tag_add('some TAG4', 1)
-    ticket1.tag_add(' some tag4', 1)
-
-    ticket2.tag_add('some tag3', 1)
-    ticket2.tag_add('some TAG4', 1)
-    ticket2.tag_add('some tag4 ', 1)
-
-    ticket1.tag_remove('some tag1', 1)
-
-    ticket1_lookup1 = Ticket.lookup(id: ticket1.id)
-    assert_not_equal(ticket1.updated_at.to_s, ticket1_lookup1.updated_at.to_s)
-    ticket2_lookup1 = Ticket.lookup(id: ticket2.id)
-    assert_not_equal(ticket2.updated_at.to_s, ticket2_lookup1.updated_at.to_s)
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(4, tags_ticket1.count)
-    assert(tags_ticket1.include?('some tag2'))
-    assert(tags_ticket1.include?('some tag3'))
-    assert(tags_ticket1.include?('some TAG4'))
-    assert(tags_ticket1.include?('some tag4'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(3, tags_ticket2.count)
-    assert(tags_ticket2.include?('some tag3'))
-    assert(tags_ticket2.include?('some TAG4'))
-    assert(tags_ticket2.include?('some tag4'))
-
-    # rename tag
-    travel 2.seconds
-    tag_item3 = Tag::Item.find_by(name: 'some tag3')
-    Tag::Item.rename(
-      id:            tag_item3.id,
-      name:          ' some tag33',
-      created_by_id: 1,
-    )
-
-    ticket1_lookup2 = Ticket.lookup(id: ticket1.id)
-    assert_not_equal(ticket1_lookup2.updated_at.to_s, ticket1_lookup1.updated_at.to_s)
-    ticket2_lookup2 = Ticket.lookup(id: ticket2.id)
-    assert_not_equal(ticket2_lookup2.updated_at.to_s, ticket2_lookup1.updated_at.to_s)
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(4, tags_ticket1.count)
-    assert(tags_ticket1.include?('some tag2'))
-    assert(tags_ticket1.include?('some tag33'))
-    assert(tags_ticket1.include?('some TAG4'))
-    assert(tags_ticket1.include?('some tag4'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(3, tags_ticket2.count)
-    assert(tags_ticket2.include?('some tag33'))
-    assert(tags_ticket2.include?('some TAG4'))
-    assert(tags_ticket2.include?('some tag4'))
-
-    # merge tags
-    travel 2.seconds
-    Tag::Item.rename(
-      id:            tag_item3.id,
-      name:          'some tag2',
-      created_by_id: 1,
-    )
-
-    ticket1_lookup3 = Ticket.lookup(id: ticket1.id)
-    assert_not_equal(ticket1_lookup3.updated_at.to_s, ticket1_lookup2.updated_at.to_s)
-    ticket2_lookup3 = Ticket.lookup(id: ticket2.id)
-    assert_not_equal(ticket2_lookup3.updated_at.to_s, ticket2_lookup2.updated_at.to_s)
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(3, tags_ticket1.count)
-    assert(tags_ticket1.include?('some tag2'))
-    assert(tags_ticket1.include?('some TAG4'))
-    assert(tags_ticket1.include?('some tag4'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(3, tags_ticket2.count)
-    assert(tags_ticket2.include?('some tag2'))
-    assert(tags_ticket2.include?('some TAG4'))
-    assert(tags_ticket2.include?('some tag4'))
-
-    assert_not(Tag::Item.find_by(id: tag_item3.id))
-
-    # remove tag item
-    travel 2.seconds
-    tag_item4 = Tag::Item.find_by(name: 'some TAG4')
-    Tag::Item.remove(tag_item4.id)
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(2, tags_ticket1.count)
-    assert(tags_ticket1.include?('some tag2'))
-    assert(tags_ticket1.include?('some tag4'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(2, tags_ticket2.count)
-    assert(tags_ticket2.include?('some tag2'))
-    assert(tags_ticket2.include?('some tag4'))
-
-    assert_not(Tag::Item.find_by(id: tag_item4.id))
-
-    ticket1_lookup4 = Ticket.lookup(id: ticket1.id)
-    assert_not_equal(ticket1_lookup4.updated_at.to_s, ticket1_lookup3.updated_at.to_s)
-    ticket2_lookup4 = Ticket.lookup(id: ticket2.id)
-    assert_not_equal(ticket2_lookup4.updated_at.to_s, ticket2_lookup3.updated_at.to_s)
-    travel_back
-  end
-
-  test 'tags - rename tag with same name' do
-
-    ticket1 = Ticket.create(
-      title:         'rename tag1',
-      group:         Group.lookup(name: 'Users'),
-      customer_id:   2,
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-    ticket2 = Ticket.create(
-      title:         'rename tag2',
-      group:         Group.lookup(name: 'Users'),
-      customer_id:   2,
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-
-    ticket1.tag_add('some rename tag1', 1)
-    ticket1.tag_add('some rename tag2 ', 1)
-
-    ticket2.tag_add('some rename tag2', 1)
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(2, tags_ticket1.count)
-    assert(tags_ticket1.include?('some rename tag1'))
-    assert(tags_ticket1.include?('some rename tag2'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(1, tags_ticket2.count)
-    assert(tags_ticket2.include?('some rename tag2'))
-
-    tag_item1 = Tag::Item.find_by(name: 'some rename tag1')
-    Tag::Item.rename(
-      id:            tag_item1.id,
-      name:          ' some rename tag1',
-      created_by_id: 1,
-    )
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(2, tags_ticket1.count)
-    assert(tags_ticket1.include?('some rename tag1'))
-    assert(tags_ticket1.include?('some rename tag2'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(1, tags_ticket2.count)
-    assert(tags_ticket2.include?('some rename tag2'))
-
-  end
-
-  test 'tags - rename and merge tag with existing tag' do
-
-    ticket1 = Ticket.create(
-      title:         'rename tag1',
-      group:         Group.lookup(name: 'Users'),
-      customer_id:   2,
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-    ticket2 = Ticket.create(
-      title:         'rename tag2',
-      group:         Group.lookup(name: 'Users'),
-      customer_id:   2,
-      updated_by_id: 1,
-      created_by_id: 1,
-    )
-
-    ticket1.tag_add('tagname1', 1)
-    ticket1.tag_add('tagname2', 1)
-
-    ticket2.tag_add('Tagname2', 1)
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(2, tags_ticket1.count)
-    assert(tags_ticket1.include?('tagname1'))
-    assert(tags_ticket1.include?('tagname2'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(1, tags_ticket2.count)
-    assert(tags_ticket2.include?('Tagname2'))
-
-    tag_item1 = Tag::Item.lookup(name: 'Tagname2')
-    Tag::Item.rename(
-      id:            tag_item1.id,
-      name:          'tagname2',
-      created_by_id: 1,
-    )
-
-    tags_ticket1 = ticket1.tag_list
-    assert_equal(2, tags_ticket1.count)
-    assert(tags_ticket1.include?('tagname1'))
-    assert(tags_ticket1.include?('tagname2'))
-
-    tags_ticket2 = ticket2.tag_list
-    assert_equal(1, tags_ticket2.count)
-    assert(tags_ticket2.include?('tagname2'))
-
-  end
-end