From 1d01ffb782a1bf44d5826666590408b24fba2332 Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Thu, 12 Jan 2017 12:45:00 +0100
Subject: [PATCH] Make it possible to combine extended CI/CD statuses

This commit also makes it possible to configure exclusive groups.
There can be only one detailed status matched within an exclusive group,
which is important from the performance perspective.
---
 lib/gitlab/ci/status/factory.rb           | 18 +++--
 spec/lib/gitlab/ci/status/factory_spec.rb | 97 +++++++++++++++++++++--
 2 files changed, 102 insertions(+), 13 deletions(-)

diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index ae9ef895df4..71c54aebcc3 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -8,10 +8,12 @@ module Gitlab
         end
 
         def fabricate!
-          if extended_status
-            extended_status.new(core_status)
-          else
+          if extended_statuses.none?
             core_status
+          else
+            extended_statuses.inject(core_status) do |status, extended|
+              extended.new(status)
+            end
           end
         end
 
@@ -36,10 +38,14 @@ module Gitlab
             .extend(self.class.common_helpers)
         end
 
-        def extended_status
-          @extended ||= self.class.extended_statuses.find do |status|
-            status.matches?(@subject, @user)
+        def extended_statuses
+          return @extended_statuses if defined?(@extended_statuses)
+
+          groups = self.class.extended_statuses.map do |group|
+            Array(group).find { |status| status.matches?(@subject, @user) }
           end
+
+          @extended_statuses = groups.flatten.compact
         end
       end
     end
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index f92a1c149bf..d78d563a9b9 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -1,18 +1,14 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Factory do
-  subject do
-    described_class.new(resource, user)
-  end
-
   let(:user) { create(:user) }
-
-  let(:status) { subject.fabricate! }
+  let(:status) { factory.fabricate! }
+  let(:factory) { described_class.new(resource, user) }
 
   context 'when object has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
-        let(:resource) { double(status: core_status) }
+        let(:resource) { double('resource', status: core_status) }
 
         it "fabricates a core status #{core_status}" do
           expect(status).to be_a(
@@ -21,4 +17,91 @@ describe Gitlab::Ci::Status::Factory do
       end
     end
   end
+
+  context 'when resource supports multiple extended statuses' do
+    let(:resource) { double('resource', status: :success) }
+
+    let(:first_extended_status) do
+      Class.new(SimpleDelegator) do
+        def first_method
+          'first return value'
+        end
+
+        def second_method
+          'second return value'
+        end
+
+        def self.matches?(*)
+          true
+        end
+      end
+    end
+
+    let(:second_extended_status) do
+      Class.new(SimpleDelegator) do
+        def first_method
+          'decorated return value'
+        end
+
+        def third_method
+          'third return value'
+        end
+
+        def self.matches?(*)
+          true
+        end
+      end
+    end
+
+    shared_examples 'compound decorator factory' do
+      it 'fabricates compound decorator' do
+        expect(status.first_method).to eq 'decorated return value'
+        expect(status.second_method).to eq 'second return value'
+        expect(status.third_method).to eq 'third return value'
+      end
+
+      it 'delegates to core status' do
+        expect(status.text).to eq 'passed'
+      end
+
+      it 'latest matches status becomes a status name' do
+        expect(status.class).to eq second_extended_status
+      end
+    end
+
+    context 'when exclusive statuses are matches' do
+      before do
+        allow(described_class).to receive(:extended_statuses)
+          .and_return([[first_extended_status, second_extended_status]])
+      end
+
+      it 'fabricates compound decorator' do
+        expect(status.first_method).to eq 'first return value'
+        expect(status.second_method).to eq 'second return value'
+        expect(status).not_to respond_to(:third_method)
+      end
+
+      it 'delegates to core status' do
+        expect(status.text).to eq 'passed'
+      end
+    end
+
+    context 'when exclusive statuses are not matched' do
+      before do
+        allow(described_class).to receive(:extended_statuses)
+          .and_return([[first_extended_status], [second_extended_status]])
+      end
+
+      it_behaves_like 'compound decorator factory'
+    end
+
+    context 'when using simplified status grouping' do
+      before do
+        allow(described_class).to receive(:extended_statuses)
+          .and_return([first_extended_status, second_extended_status])
+      end
+
+      it_behaves_like 'compound decorator factory'
+    end
+  end
 end
-- 
2.30.9