From e55071ecfb409e1b7561c0b68f33b6eb90c0d317 Mon Sep 17 00:00:00 2001
From: Kassio Borges <kborges@gitlab.com>
Date: Wed, 19 Jun 2024 15:07:05 +0000
Subject: [PATCH] Add missing ResourceLinkEvent#synthetic_note_class

Related to:
- https://gitlab.com/gitlab-org/gitlab/-/issues/467243
- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114394
- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107584

Changelog: fixed
---
 .../concerns/work_item_resource_event.rb      | 13 +++-
 app/models/resource_event.rb                  |  2 +-
 app/models/work_items/resource_link_event.rb  |  4 ++
 spec/models/resource_event_spec.rb            | 63 ++++++++++++++++---
 .../models/resource_event_shared_examples.rb  |  7 +++
 5 files changed, 76 insertions(+), 13 deletions(-)

diff --git a/app/models/concerns/work_item_resource_event.rb b/app/models/concerns/work_item_resource_event.rb
index ddf39787f63b8..d3f8419a77eea 100644
--- a/app/models/concerns/work_item_resource_event.rb
+++ b/app/models/concerns/work_item_resource_event.rb
@@ -20,8 +20,10 @@ def trigger_note_subscription_create(events: self)
   end
 
   def work_item_synthetic_system_note(events: nil)
-    # System notes for label resource events are handled in batches, so that we have single system note for multiple
-    # label changes.
+    return unless synthetic_note_class
+
+    # System notes for label resource events are handled in batches,
+    # so that we have single system note for multiple label changes.
     if is_a?(ResourceLabelEvent) && events.present?
       return synthetic_note_class.from_events(events, resource: work_item, resource_parent: work_item.project)
     end
@@ -29,7 +31,12 @@ def work_item_synthetic_system_note(events: nil)
     synthetic_note_class.from_event(self, resource: work_item, resource_parent: work_item.project)
   end
 
+  # Class used to create the even synthetic note
+  # If the event does not require a synthetic note the method must return false
   def synthetic_note_class
-    raise NoMethodError, 'must implement `synthetic_note_class` method'
+    raise NoMethodError, <<~MESSAGE.squish
+      `#{self.class.name}#synthetic_note_class` method must be implemented
+      (return nil if event does not require a note)
+    MESSAGE
   end
 end
diff --git a/app/models/resource_event.rb b/app/models/resource_event.rb
index 551ea98413295..88c1576a41bf4 100644
--- a/app/models/resource_event.rb
+++ b/app/models/resource_event.rb
@@ -21,7 +21,7 @@ def discussion_id
   end
 
   def issuable
-    raise NoMethodError, 'must implement `issuable` method'
+    raise NoMethodError, "`#{self.class.name}#issuable` method must be implemented"
   end
 
   private
diff --git a/app/models/work_items/resource_link_event.rb b/app/models/work_items/resource_link_event.rb
index 6725acf8c68c9..b0f52b447e50d 100644
--- a/app/models/work_items/resource_link_event.rb
+++ b/app/models/work_items/resource_link_event.rb
@@ -10,6 +10,10 @@ class ResourceLinkEvent < ResourceEvent
       add: 1,
       remove: 2
     }
+
+    def synthetic_note_class
+      nil
+    end
   end
 end
 
diff --git a/spec/models/resource_event_spec.rb b/spec/models/resource_event_spec.rb
index 62bd5314b6932..e141c2e4632ff 100644
--- a/spec/models/resource_event_spec.rb
+++ b/spec/models/resource_event_spec.rb
@@ -3,17 +3,62 @@
 require 'spec_helper'
 
 RSpec.describe ResourceEvent, feature_category: :team_planning, type: :model do
-  let(:dummy_resource_label_event_class) do
-    Class.new(ResourceEvent) do
-      self.table_name = 'resource_label_events'
+  context 'when inheriting from ResourceEvent' do
+    context 'when it does not implement the #issuable method' do
+      let(:dummy_resource_label_event_class) do
+        Class.new(ResourceEvent) do
+          self.table_name = 'resource_label_events'
+
+          def self.name
+            'DummyResourceLabelEventClass'
+          end
+        end
+      end
+
+      it 'raises error on not implemented `issuable` method' do
+        expect { dummy_resource_label_event_class.new.issuable }
+          .to raise_error(
+            NoMethodError,
+            "`DummyResourceLabelEventClass#issuable` method must be implemented"
+          )
+      end
     end
-  end
 
-  it 'raises error on not implemented `issuable` method' do
-    expect { dummy_resource_label_event_class.new.issuable }.to raise_error(NoMethodError)
-  end
+    context 'when it does not implement the #synthetic_note_class method' do
+      let(:dummy_resource_label_event_class) do
+        Class.new(ResourceEvent) do
+          self.table_name = 'resource_label_events'
+
+          def self.name
+            'DummyResourceLabelEventClass'
+          end
+
+          def issuable
+            :issuable
+          end
+        end
+      end
 
-  it 'raises error on not implemented `synthetic_note_class` method' do
-    expect { dummy_resource_label_event_class.new.synthetic_note_class }.to raise_error(NoMethodError)
+      it 'raises error on not implemented `issuable` method' do
+        expect { dummy_resource_label_event_class.new.synthetic_note_class }
+          .to raise_error(NoMethodError, <<~MESSAGE.squish)
+            `DummyResourceLabelEventClass#synthetic_note_class` method must be implemented
+            (return nil if event does not require a note)
+          MESSAGE
+      end
+    end
+
+    it 'must implement #synthetic_note_class method', :aggregate_failures do
+      Dir['{ee/,}app/models/**/resource*event.rb'].each do |klass|
+        require(Rails.root.join(klass))
+      end
+
+      described_class.subclasses.each do |klass|
+        next if klass.abstract_class?
+
+        expect { klass.new.synthetic_note_class }
+          .not_to(raise_error)
+      end
+    end
   end
 end
diff --git a/spec/support/shared_examples/models/resource_event_shared_examples.rb b/spec/support/shared_examples/models/resource_event_shared_examples.rb
index 853d6635305ea..b3cd7281a3070 100644
--- a/spec/support/shared_examples/models/resource_event_shared_examples.rb
+++ b/spec/support/shared_examples/models/resource_event_shared_examples.rb
@@ -53,6 +53,13 @@
       expect(events).to be_empty
     end
   end
+
+  describe '#synthetic_note_class' do
+    it 'must implement #synthetic_note_class method' do
+      expect { described_class.new.synthetic_note_class }
+        .not_to raise_error
+    end
+  end
 end
 
 RSpec.shared_examples 'a resource event that responds to imported' do
-- 
GitLab