From ace0df53d3ed38344b470727d430484d24eeb798 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 30 Nov 2020 18:09:46 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/db/schema_spec.rb | 2 +- spec/features/profiles/user_edit_profile_spec.rb | 41 +++ .../api/schemas/entities/note_user_entity.json | 3 +- spec/frontend/diffs/components/app_spec.js | 10 +- spec/frontend/notes/components/note_header_spec.js | 37 +- spec/helpers/markup_helper_spec.rb | 18 +- .../pull_request_merged_by_importer_spec.rb | 4 +- spec/lib/gitlab/kubernetes/deployment_spec.rb | 190 ++++++++++ spec/lib/gitlab/kubernetes/ingress_spec.rb | 57 +++ .../gitlab/kubernetes/rollout_instances_spec.rb | 128 +++++++ spec/lib/gitlab/kubernetes/rollout_status_spec.rb | 271 ++++++++++++++ spec/models/clusters/platforms/kubernetes_spec.rb | 399 ++++++++++++++++++++- spec/models/environment_spec.rb | 119 ++++++ spec/serializers/rollout_status_entity_spec.rb | 53 +++ .../rollout_statuses/ingress_entity_spec.rb | 19 + 15 files changed, 1329 insertions(+), 22 deletions(-) create mode 100644 spec/lib/gitlab/kubernetes/deployment_spec.rb create mode 100644 spec/lib/gitlab/kubernetes/ingress_spec.rb create mode 100644 spec/lib/gitlab/kubernetes/rollout_instances_spec.rb create mode 100644 spec/lib/gitlab/kubernetes/rollout_status_spec.rb create mode 100644 spec/serializers/rollout_status_entity_spec.rb create mode 100644 spec/serializers/rollout_statuses/ingress_entity_spec.rb (limited to 'spec') diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index c35f3831a58..a06ba4f229a 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -86,7 +86,7 @@ RSpec.describe 'Database schema' do users_star_projects: %w[user_id], vulnerability_identifiers: %w[external_id], vulnerability_scanners: %w[external_id], - web_hooks: %w[service_id group_id] + web_hooks: %w[group_id] }.with_indifferent_access.freeze context 'for table' do diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index d0340dfc880..f341709b73d 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'User edit profile' do + include Spec::Support::Helpers::Features::NotesHelpers + let(:user) { create(:user) } before do @@ -398,6 +400,45 @@ RSpec.describe 'User edit profile' do end end + context 'note header' do + let(:project) { create(:project_empty_repo, :public) } + let(:issue) { create(:issue, project: project) } + let(:emoji) { "stuffed_flatbread" } + + before do + project.add_guest(user) + create(:user_status, user: user, message: 'Taking notes', emoji: emoji) + + visit(project_issue_path(project, issue)) + + add_note("This is a comment") + visit(project_issue_path(project, issue)) + + wait_for_requests + end + + it 'displays the status emoji' do + first_note = page.find_all(".main-notes-list .timeline-entry").first + + expect(first_note).to have_emoji(emoji) + end + + it 'clears the status emoji' do + open_edit_status_modal + + page.within "#set-user-status-modal" do + click_button 'Remove status' + end + + visit(project_issue_path(project, issue)) + wait_for_requests + + first_note = page.find_all(".main-notes-list .timeline-entry").first + + expect(first_note).not_to have_css('.user-status-emoji') + end + end + context 'with set_user_availability_status feature flag disabled' do before do stub_feature_flags(set_user_availability_status: false) diff --git a/spec/fixtures/api/schemas/entities/note_user_entity.json b/spec/fixtures/api/schemas/entities/note_user_entity.json index 4a27d885cdc..e2bbaad7201 100644 --- a/spec/fixtures/api/schemas/entities/note_user_entity.json +++ b/spec/fixtures/api/schemas/entities/note_user_entity.json @@ -15,6 +15,7 @@ "path": { "type": "string" }, "name": { "type": "string" }, "username": { "type": "string" }, - "status_tooltip_html": { "$ref": "../types/nullable_string.json" } + "status_tooltip_html": { "$ref": "../types/nullable_string.json" }, + "show_status": { "type": "boolean" } } } diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index 5982b88737c..01c789270da 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -678,8 +678,12 @@ describe('diffs/components/app', () => { expect(setShowTreeList).toHaveBeenCalledWith({ showTreeList: true, saving: false }); }); - it('calls setShowTreeList with localstorage value', () => { - localStorage.setItem('mr_tree_show', 'true'); + it.each` + showTreeList + ${true} + ${false} + `('calls setShowTreeList with localstorage $showTreeList', ({ showTreeList }) => { + localStorage.setItem('mr_tree_show', showTreeList); createComponent({}, ({ state }) => { state.diffs.diffFiles.push({ sha: '123' }); @@ -691,7 +695,7 @@ describe('diffs/components/app', () => { wrapper.vm.setTreeDisplay(); - expect(setShowTreeList).toHaveBeenCalledWith({ showTreeList: true, saving: false }); + expect(setShowTreeList).toHaveBeenCalledWith({ showTreeList, saving: false }); }); }); diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js index 69aab0d051e..1c6d0bafda8 100644 --- a/spec/frontend/notes/components/note_header_spec.js +++ b/spec/frontend/notes/components/note_header_spec.js @@ -22,6 +22,10 @@ describe('NoteHeader component', () => { const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' }); const findConfidentialIndicator = () => wrapper.find('[data-testid="confidentialIndicator"]'); const findSpinner = () => wrapper.find({ ref: 'spinner' }); + const findAuthorStatus = () => wrapper.find({ ref: 'authorStatus' }); + + const statusHtml = + '"🏀"'; const author = { avatar_url: null, @@ -30,6 +34,8 @@ describe('NoteHeader component', () => { path: '/root', state: 'active', username: 'root', + show_status: true, + status_tooltip_html: statusHtml, status: { availability: '', }, @@ -109,6 +115,32 @@ describe('NoteHeader component', () => { expect(wrapper.find('.js-user-link').text()).toContain('(Busy)'); }); + it('renders author status', () => { + createComponent({ author }); + + expect(findAuthorStatus().exists()).toBe(true); + }); + + it('does not render author status if show_status=false', () => { + createComponent({ + author: { ...author, status: { availability: AVAILABILITY_STATUS.BUSY }, show_status: false }, + }); + + expect(findAuthorStatus().exists()).toBe(false); + }); + + it('does not render author status if status_tooltip_html=null', () => { + createComponent({ + author: { + ...author, + status: { availability: AVAILABILITY_STATUS.BUSY }, + status_tooltip_html: null, + }, + }); + + expect(findAuthorStatus().exists()).toBe(false); + }); + it('renders deleted user text if author is not passed as a prop', () => { createComponent(); @@ -206,13 +238,12 @@ describe('NoteHeader component', () => { createComponent({ author: { ...author, - status_tooltip_html: - '"🏀"', + status_tooltip_html: statusHtml, }, }); return nextTick().then(() => { - const authorStatus = wrapper.find({ ref: 'authorStatus' }); + const authorStatus = findAuthorStatus(); authorStatus.trigger('mouseenter'); expect(authorStatus.find('gl-emoji').attributes('title')).toBeUndefined(); diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 6c5855eeb91..45e8a2e7e1a 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -316,6 +316,7 @@ RSpec.describe MarkupHelper do describe '#render_wiki_content' do let(:wiki) { double('WikiPage', path: "file.#{extension}") } let(:wiki_repository) { double('Repository') } + let(:content) { 'wiki content' } let(:context) do { pipeline: :wiki, project: project, wiki: wiki, @@ -325,9 +326,11 @@ RSpec.describe MarkupHelper do end before do - expect(wiki).to receive(:content).and_return('wiki content') + expect(wiki).to receive(:content).and_return(content) expect(wiki).to receive(:slug).and_return('nested/page') expect(wiki).to receive(:repository).and_return(wiki_repository) + allow(wiki).to receive(:container).and_return(project) + helper.instance_variable_set(:@wiki, wiki) end @@ -339,6 +342,19 @@ RSpec.describe MarkupHelper do helper.render_wiki_content(wiki) end + + context 'when context has labels' do + let_it_be(:label) { create(:label, title: 'Bug', project: project) } + + let(:content) { '~Bug' } + + it 'renders label' do + result = helper.render_wiki_content(wiki) + doc = Nokogiri::HTML.parse(result) + + expect(doc.css('.gl-label-link')).not_to be_empty + end + end end context 'when file is Asciidoc' do diff --git a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb index bed428e0327..2999dc5bb41 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb @@ -28,7 +28,9 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :cle end it 'adds a note referencing the merger user when the user cannot be mapped' do - expect { subject.execute }.to change(Note, :count).by(1) + expect { subject.execute } + .to change(Note, :count).by(1) + .and not_change(merge_request, :updated_at) last_note = merge_request.notes.last diff --git a/spec/lib/gitlab/kubernetes/deployment_spec.rb b/spec/lib/gitlab/kubernetes/deployment_spec.rb new file mode 100644 index 00000000000..2433e854e5b --- /dev/null +++ b/spec/lib/gitlab/kubernetes/deployment_spec.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::Deployment do + include KubernetesHelpers + + let(:pods) { {} } + + subject(:deployment) { described_class.new(params, pods: pods) } + + describe '#name' do + let(:params) { named(:selected) } + + it { expect(deployment.name).to eq(:selected) } + end + + describe '#labels' do + let(:params) { make('metadata', 'labels' => :selected) } + + it { expect(deployment.labels).to eq(:selected) } + end + + describe '#outdated?' do + context 'when outdated' do + let(:params) { generation(2, 1, 0) } + + it { expect(deployment.outdated?).to be_truthy } + end + + context 'when up to date' do + let(:params) { generation(2, 2, 0) } + + it { expect(deployment.outdated?).to be_falsy } + end + + context 'when ahead of latest' do + let(:params) { generation(1, 2, 0) } + + it { expect(deployment.outdated?).to be_falsy } + end + end + + describe '#instances' do + context 'when unnamed' do + let(:pods) do + [ + kube_pod(name: nil, status: 'Pending'), + kube_pod(name: nil, status: 'Pending'), + kube_pod(name: nil, status: 'Pending'), + kube_pod(name: nil, status: 'Pending') + ] + end + + let(:params) { combine(generation(1, 1, 4)) } + + it 'returns all pods with generated names and pending' do + expected = [ + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + # When replica count is higher than pods it is considered that pod was not + # able to spawn for some reason like limited resources. + context 'when number of pods is less than wanted replicas' do + let(:wanted_replicas) { 3 } + let(:pods) { [kube_pod(name: nil, status: 'Running')] } + let(:params) { combine(generation(1, 1, wanted_replicas)) } + + it 'returns not spawned pods as pending and unknown and running' do + expected = [ + { status: 'running', pod_name: 'generated-name-with-suffix', tooltip: 'generated-name-with-suffix (Running)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'Not provided', tooltip: 'Not provided (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'Not provided', tooltip: 'Not provided (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'when outdated' do + let(:pods) do + [ + kube_pod(status: 'Pending'), + kube_pod(name: 'kube-pod1', status: 'Pending'), + kube_pod(name: 'kube-pod2', status: 'Pending'), + kube_pod(name: 'kube-pod3', status: 'Pending') + ] + end + + let(:params) { combine(named('foo'), generation(1, 0, 4)) } + + it 'returns all instances as named and waiting' do + expected = [ + { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod1', tooltip: 'kube-pod1 (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod2', tooltip: 'kube-pod2 (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod3', tooltip: 'kube-pod3 (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'with pods of each type' do + let(:pods) do + [ + kube_pod(status: 'Succeeded'), + kube_pod(name: 'kube-pod1', status: 'Running'), + kube_pod(name: 'kube-pod2', status: 'Pending'), + kube_pod(name: 'kube-pod3', status: 'Pending') + ] + end + + let(:params) { combine(named('foo'), generation(1, 1, 4)) } + + it 'returns all instances' do + expected = [ + { status: 'succeeded', pod_name: 'kube-pod', tooltip: 'kube-pod (Succeeded)', track: 'stable', stable: true }, + { status: 'running', pod_name: 'kube-pod1', tooltip: 'kube-pod1 (Running)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod2', tooltip: 'kube-pod2 (Pending)', track: 'stable', stable: true }, + { status: 'pending', pod_name: 'kube-pod3', tooltip: 'kube-pod3 (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'with track label' do + let(:pods) { [kube_pod(status: 'Pending')] } + let(:labels) { { 'track' => track } } + let(:params) { combine(named('foo', labels), generation(1, 0, 1)) } + + context 'when marked as stable' do + let(:track) { 'stable' } + + it 'returns all instances' do + expected = [ + { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'stable', stable: true } + ] + + expect(deployment.instances).to eq(expected) + end + end + + context 'when marked as canary' do + let(:track) { 'canary' } + let(:pods) { [kube_pod(status: 'Pending', track: track)] } + + it 'returns all instances' do + expected = [ + { status: 'pending', pod_name: 'kube-pod', tooltip: 'kube-pod (Pending)', track: 'canary', stable: false } + ] + + expect(deployment.instances).to eq(expected) + end + end + end + end + + def generation(expected, observed, replicas) + combine( + make('metadata', 'generation' => expected), + make('status', 'observedGeneration' => observed), + make('spec', 'replicas' => replicas) + ) + end + + def named(name = "foo", labels = {}) + make('metadata', 'name' => name, 'labels' => labels) + end + + def make(key, values = {}) + hsh = {} + hsh[key] = values + hsh + end + + def combine(*hashes) + out = {} + hashes.each { |hsh| out = out.deep_merge(hsh) } + out + end +end diff --git a/spec/lib/gitlab/kubernetes/ingress_spec.rb b/spec/lib/gitlab/kubernetes/ingress_spec.rb new file mode 100644 index 00000000000..e4d6bf4086f --- /dev/null +++ b/spec/lib/gitlab/kubernetes/ingress_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::Ingress do + include KubernetesHelpers + + let(:ingress) { described_class.new(params) } + + describe '#canary?' do + subject { ingress.canary? } + + context 'with canary ingress parameters' do + let(:params) { canary_metadata } + + it { is_expected.to be_truthy } + end + + context 'with stable ingress parameters' do + let(:params) { stable_metadata } + + it { is_expected.to be_falsey } + end + end + + describe '#canary_weight' do + subject { ingress.canary_weight } + + context 'with canary ingress parameters' do + let(:params) { canary_metadata } + + it { is_expected.to eq(50) } + end + + context 'with stable ingress parameters' do + let(:params) { stable_metadata } + + it { is_expected.to be_nil } + end + end + + describe '#name' do + subject { ingress.name } + + let(:params) { stable_metadata } + + it { is_expected.to eq('production-auto-deploy') } + end + + def stable_metadata + kube_ingress(track: :stable) + end + + def canary_metadata + kube_ingress(track: :canary) + end +end diff --git a/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb b/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb new file mode 100644 index 00000000000..3ac97ddc75d --- /dev/null +++ b/spec/lib/gitlab/kubernetes/rollout_instances_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::RolloutInstances do + include KubernetesHelpers + + def setup(deployments_attrs, pods_attrs) + deployments = deployments_attrs.map do |attrs| + ::Gitlab::Kubernetes::Deployment.new(attrs, pods: pods_attrs) + end + + pods = pods_attrs.map do |attrs| + ::Gitlab::Kubernetes::Pod.new(attrs) + end + + [deployments, pods] + end + + describe '#pod_instances' do + it 'returns an instance for a deployment with one pod' do + deployments, pods = setup( + [kube_deployment(name: 'one', track: 'stable', replicas: 1)], + [kube_pod(name: 'one', status: 'Running', track: 'stable')] + ) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'one', + stable: true, + status: 'running', + tooltip: 'one (Running)', + track: 'stable' + }]) + end + + it 'returns a pending pod for a missing replica' do + deployments, pods = setup( + [kube_deployment(name: 'one', track: 'stable', replicas: 1)], + [] + ) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'Not provided', + stable: true, + status: 'pending', + tooltip: 'Not provided (Pending)', + track: 'stable' + }]) + end + + it 'returns instances when there are two stable deployments' do + deployments, pods = setup([ + kube_deployment(name: 'one', track: 'stable', replicas: 1), + kube_deployment(name: 'two', track: 'stable', replicas: 1) + ], [ + kube_pod(name: 'one', status: 'Running', track: 'stable'), + kube_pod(name: 'two', status: 'Running', track: 'stable') + ]) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'one', + stable: true, + status: 'running', + tooltip: 'one (Running)', + track: 'stable' + }, { + pod_name: 'two', + stable: true, + status: 'running', + tooltip: 'two (Running)', + track: 'stable' + }]) + end + + it 'returns instances for two deployments with different tracks' do + deployments, pods = setup([ + kube_deployment(name: 'one', track: 'mytrack', replicas: 1), + kube_deployment(name: 'two', track: 'othertrack', replicas: 1) + ], [ + kube_pod(name: 'one', status: 'Running', track: 'mytrack'), + kube_pod(name: 'two', status: 'Running', track: 'othertrack') + ]) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'one', + stable: false, + status: 'running', + tooltip: 'one (Running)', + track: 'mytrack' + }, { + pod_name: 'two', + stable: false, + status: 'running', + tooltip: 'two (Running)', + track: 'othertrack' + }]) + end + + it 'sorts stable tracks after canary tracks' do + deployments, pods = setup([ + kube_deployment(name: 'one', track: 'stable', replicas: 1), + kube_deployment(name: 'two', track: 'canary', replicas: 1) + ], [ + kube_pod(name: 'one', status: 'Running', track: 'stable'), + kube_pod(name: 'two', status: 'Running', track: 'canary') + ]) + rollout_instances = described_class.new(deployments, pods) + + expect(rollout_instances.pod_instances).to eq([{ + pod_name: 'two', + stable: false, + status: 'running', + tooltip: 'two (Running)', + track: 'canary' + }, { + pod_name: 'one', + stable: true, + status: 'running', + tooltip: 'one (Running)', + track: 'stable' + }]) + end + end +end diff --git a/spec/lib/gitlab/kubernetes/rollout_status_spec.rb b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb new file mode 100644 index 00000000000..8ed9fdd799c --- /dev/null +++ b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb @@ -0,0 +1,271 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Kubernetes::RolloutStatus do + include KubernetesHelpers + + let(:track) { nil } + let(:specs) { specs_all_finished } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "canary") + end + + let(:ingresses) { [] } + + let(:specs_all_finished) do + [ + kube_deployment(name: 'one'), + kube_deployment(name: 'two', track: track) + ] + end + + let(:specs_half_finished) do + [ + kube_deployment(name: 'one'), + kube_deployment(name: 'two', track: track) + ] + end + + subject(:rollout_status) { described_class.from_deployments(*specs, pods_attrs: pods, ingresses: ingresses) } + + describe '#deployments' do + it 'stores the deployments' do + expect(rollout_status.deployments).to be_kind_of(Array) + expect(rollout_status.deployments.size).to eq(2) + expect(rollout_status.deployments.first).to be_kind_of(::Gitlab::Kubernetes::Deployment) + end + end + + describe '#instances' do + context 'for stable track' do + let(:track) { "any" } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "any") + end + + it 'stores the union of deployment instances' do + expected = [ + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'any', stable: false }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true } + ] + + expect(rollout_status.instances).to eq(expected) + end + end + + context 'for stable track' do + let(:track) { 'canary' } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track) + end + + it 'sorts stable instances last' do + expected = [ + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false }, + { status: 'running', pod_name: "two", tooltip: 'two (Running)', track: 'canary', stable: false }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true }, + { status: 'running', pod_name: "one", tooltip: 'one (Running)', track: 'stable', stable: true } + ] + + expect(rollout_status.instances).to eq(expected) + end + end + end + + describe '#completion' do + subject { rollout_status.completion } + + context 'when all instances are finished' do + let(:track) { 'canary' } + + it { is_expected.to eq(100) } + end + + context 'when half of the instances are finished' do + let(:track) { "canary" } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending") + end + + let(:specs) { specs_half_finished } + + it { is_expected.to eq(50) } + end + + context 'with one deployment' do + it 'sets the completion percentage when a deployment has more running pods than desired' do + deployments = [kube_deployment(name: 'one', track: 'one', replicas: 2)] + pods = create_pods(name: 'one', track: 'one', count: 3) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + end + + context 'with two deployments on different tracks' do + it 'sets the completion percentage when all pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'one', replicas: 2), + kube_deployment(name: 'two', track: 'two', replicas: 2) + ] + pods = create_pods(name: 'one', track: 'one', count: 2) + create_pods(name: 'two', track: 'two', count: 2) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + end + + context 'with two deployments that both have track set to "stable"' do + it 'sets the completion percentage when all pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 2), + kube_deployment(name: 'two', track: 'stable', replicas: 2) + ] + pods = create_pods(name: 'one', track: 'stable', count: 2) + create_pods(name: 'two', track: 'stable', count: 2) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + + it 'sets the completion percentage when no pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 3), + kube_deployment(name: 'two', track: 'stable', replicas: 7) + ] + rollout_status = described_class.from_deployments(*deployments, pods_attrs: []) + + expect(rollout_status.completion).to eq(0) + end + + it 'sets the completion percentage when a quarter of the pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 6), + kube_deployment(name: 'two', track: 'stable', replicas: 2) + ] + pods = create_pods(name: 'one', track: 'stable', count: 2) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(25) + end + end + + context 'with two deployments, one with track set to "stable" and one with no track label' do + it 'sets the completion percentage when all pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 3), + kube_deployment(name: 'two', track: nil, replicas: 3) + ] + pods = create_pods(name: 'one', track: 'stable', count: 3) + create_pods(name: 'two', track: nil, count: 3) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(100) + end + + it 'sets the completion percentage when no pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 1), + kube_deployment(name: 'two', track: nil, replicas: 1) + ] + rollout_status = described_class.from_deployments(*deployments, pods_attrs: []) + + expect(rollout_status.completion).to eq(0) + end + + it 'sets the completion percentage when a third of the pods are complete' do + deployments = [ + kube_deployment(name: 'one', track: 'stable', replicas: 2), + kube_deployment(name: 'two', track: nil, replicas: 7) + ] + pods = create_pods(name: 'one', track: 'stable', count: 2) + create_pods(name: 'two', track: nil, count: 1) + rollout_status = described_class.from_deployments(*deployments, pods_attrs: pods) + + expect(rollout_status.completion).to eq(33) + end + end + end + + describe '#complete?' do + subject { rollout_status.complete? } + + context 'when all instances are finished' do + let(:track) { 'canary' } + + it { is_expected.to be_truthy } + end + + context 'when half of the instances are finished' do + let(:track) { "canary" } + + let(:pods) do + create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: track, status: "Pending") + end + + let(:specs) { specs_half_finished } + + it { is_expected.to be_falsy} + end + end + + describe '#found?' do + context 'when the specs are passed' do + it { is_expected.to be_found } + end + + context 'when list of specs is empty' do + let(:specs) { [] } + + it { is_expected.not_to be_found } + end + end + + describe '.loading' do + subject { described_class.loading } + + it { is_expected.to be_loading } + end + + describe '#not_found?' do + context 'when the specs are passed' do + it { is_expected.not_to be_not_found } + end + + context 'when list of specs is empty' do + let(:specs) { [] } + + it { is_expected.to be_not_found } + end + end + + describe '#canary_ingress_exists?' do + context 'when canary ingress exists' do + let(:ingresses) { [kube_ingress(track: :canary)] } + + it 'returns true' do + expect(rollout_status.canary_ingress_exists?).to eq(true) + end + end + + context 'when canary ingress does not exist' do + let(:ingresses) { [kube_ingress(track: :stable)] } + + it 'returns false' do + expect(rollout_status.canary_ingress_exists?).to eq(false) + end + end + end + + def create_pods(name:, count:, track: nil, status: 'Running' ) + Array.new(count, kube_pod(name: name, status: status, track: track)) + end +end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index e877ba2ac96..fb0613187c5 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Clusters::Platforms::Kubernetes do include KubernetesHelpers + include ReactiveCachingHelpers it { is_expected.to belong_to(:cluster) } it { is_expected.to be_kind_of(Gitlab::Kubernetes) } @@ -406,32 +407,62 @@ RSpec.describe Clusters::Platforms::Kubernetes do end describe '#calculate_reactive_cache_for' do + let(:cluster) { create(:cluster, :project, platform_kubernetes: service) } let(:service) { create(:cluster_platform_kubernetes, :configured) } + let(:namespace) { 'project-namespace' } + let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: cluster.project) } let(:expected_pod_cached_data) do kube_pod.tap { |kp| kp['metadata'].delete('namespace') } end - let(:namespace) { "project-namespace" } - let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: service.cluster.project) } - subject { service.calculate_reactive_cache_for(environment) } - context 'when the kubernetes integration is disabled' do + context 'when kubernetes responds with valid deployments' do before do - allow(service).to receive(:enabled?).and_return(false) + stub_kubeclient_pods(namespace) + stub_kubeclient_deployments(namespace) + stub_kubeclient_ingresses(namespace) end - it { is_expected.to be_nil } + shared_examples 'successful deployment request' do + it { is_expected.to include(pods: [expected_pod_cached_data], deployments: [kube_deployment], ingresses: [kube_ingress]) } + end + + context 'on a project level cluster' do + let(:cluster) { create(:cluster, :project, platform_kubernetes: service) } + + include_examples 'successful deployment request' + end + + context 'on a group level cluster' do + let(:cluster) { create(:cluster, :group, platform_kubernetes: service) } + + include_examples 'successful deployment request' + end + + context 'on an instance level cluster' do + let(:cluster) { create(:cluster, :instance, platform_kubernetes: service) } + + include_examples 'successful deployment request' + end + + context 'when canary_ingress_weight_control feature flag is disabled' do + before do + stub_feature_flags(canary_ingress_weight_control: false) + end + + it 'does not fetch ingress data from kubernetes' do + expect(subject[:ingresses]).to be_empty + end + end end - context 'when kubernetes responds with valid pods and deployments' do + context 'when the kubernetes integration is disabled' do before do - stub_kubeclient_pods(namespace) - stub_kubeclient_deployments(namespace) - stub_kubeclient_ingresses(namespace) + allow(service).to receive(:enabled?).and_return(false) end - it { is_expected.to include(pods: [expected_pod_cached_data]) } + it { is_expected.to be_nil } end context 'when kubernetes responds with 500s' do @@ -451,7 +482,351 @@ RSpec.describe Clusters::Platforms::Kubernetes do stub_kubeclient_ingresses(namespace, status: 404) end - it { is_expected.to include(pods: []) } + it { is_expected.to eq(pods: [], deployments: [], ingresses: []) } + end + end + + describe '#rollout_status' do + let(:deployments) { [] } + let(:pods) { [] } + let(:ingresses) { [] } + let(:service) { create(:cluster_platform_kubernetes, :configured) } + let!(:cluster) { create(:cluster, :project, enabled: true, platform_kubernetes: service) } + let(:project) { cluster.project } + let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") } + let(:cache_data) { Hash(deployments: deployments, pods: pods, ingresses: ingresses) } + + subject(:rollout_status) { service.rollout_status(environment, cache_data) } + + context 'legacy deployments based on app label' do + let(:legacy_deployment) do + kube_deployment(name: 'legacy-deployment').tap do |deployment| + deployment['metadata']['annotations'].delete('app.gitlab.com/env') + deployment['metadata']['annotations'].delete('app.gitlab.com/app') + deployment['metadata']['labels']['app'] = environment.slug + end + end + + let(:legacy_pod) do + kube_pod(name: 'legacy-pod').tap do |pod| + pod['metadata']['annotations'].delete('app.gitlab.com/env') + pod['metadata']['annotations'].delete('app.gitlab.com/app') + pod['metadata']['labels']['app'] = environment.slug + end + end + + context 'only legacy deployments' do + let(:deployments) { [legacy_deployment] } + let(:pods) { [legacy_pod] } + + it 'contains nothing' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + + expect(rollout_status.deployments).to eq([]) + end + end + + context 'deployment with no pods' do + let(:deployment) { kube_deployment(name: 'some-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:deployments) { [deployment] } + let(:pods) { [] } + + it 'returns a valid status with matching deployments' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + expect(rollout_status.deployments.map(&:name)).to contain_exactly('some-deployment') + end + end + + context 'new deployment based on annotations' do + let(:matched_deployment) { kube_deployment(name: 'matched-deployment', environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) } + let(:deployments) { [matched_deployment, legacy_deployment] } + let(:pods) { [matched_pod, legacy_pod] } + + it 'contains only matching deployments' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + + expect(rollout_status.deployments.map(&:name)).to contain_exactly('matched-deployment') + end + end + end + + context 'with no deployments but there are pods' do + let(:deployments) do + [] + end + + let(:pods) do + [ + kube_pod(name: 'pod-1', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-2', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'returns an empty array' do + expect(rollout_status.instances).to eq([]) + end + end + + context 'with valid deployments' do + let(:matched_deployment) { kube_deployment(environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2) } + let(:unmatched_deployment) { kube_deployment } + let(:matched_pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: 'Pending') } + let(:unmatched_pod) { kube_pod(environment_slug: environment.slug + '-test', project_slug: project.full_path_slug) } + let(:deployments) { [matched_deployment, unmatched_deployment] } + let(:pods) { [matched_pod, unmatched_pod] } + + it 'creates a matching RolloutStatus' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + expect(rollout_status.deployments.map(&:annotations)).to eq([ + { 'app.gitlab.com/app' => project.full_path_slug, 'app.gitlab.com/env' => 'env-000000' } + ]) + expect(rollout_status.instances).to eq([{ pod_name: "kube-pod", + stable: true, + status: "pending", + tooltip: "kube-pod (Pending)", + track: "stable" }, + { pod_name: "Not provided", + stable: true, + status: "pending", + tooltip: "Not provided (Pending)", + track: "stable" }]) + end + + context 'with canary ingress' do + let(:ingresses) { [kube_ingress(track: :canary)] } + + it 'has canary ingress' do + expect(rollout_status).to be_canary_ingress_exists + expect(rollout_status.canary_ingress.canary_weight).to eq(50) + end + end + end + + context 'with empty list of deployments' do + it 'creates a matching RolloutStatus' do + expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus) + expect(rollout_status).to be_not_found + end + end + + context 'when the pod track does not match the deployment track' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'weekly') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'weekly'), + kube_pod(name: 'pod-a-2', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'daily') + ] + end + + it 'does not return the pod' do + expect(rollout_status.instances.map { |p| p[:pod_name] }).to eq(['pod-a-1']) + end + end + + context 'when the pod track is not stable' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'something') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'something') + ] + end + + it 'the pod is not stable' do + expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: false, track: 'something' }]) + end + end + + context 'when the pod track is stable' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'stable') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'stable') + ] + end + + it 'the pod is stable' do + expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: true, track: 'stable' }]) + end + end + + context 'when the pod track is not provided' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1) + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'the pod is stable' do + expect(rollout_status.instances.map { |p| p.slice(:stable, :track) }).to eq([{ stable: true, track: 'stable' }]) + end + end + + context 'when the number of matching pods does not match the number of replicas' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 3) + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'returns a pending pod for each missing replica' do + expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status) }).to eq([ + { pod_name: 'pod-a-1', status: 'running' }, + { pod_name: 'Not provided', status: 'pending' }, + { pod_name: 'Not provided', status: 'pending' } + ]) + end + end + + context 'when pending pods are returned for missing replicas' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2, track: 'canary'), + kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2, track: 'stable') + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug, track: 'canary') + ] + end + + it 'returns the correct track for the pending pods' do + expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([ + { pod_name: 'pod-a-1', status: 'running', track: 'canary' }, + { pod_name: 'Not provided', status: 'pending', track: 'canary' }, + { pod_name: 'Not provided', status: 'pending', track: 'stable' }, + { pod_name: 'Not provided', status: 'pending', track: 'stable' } + ]) + end + end + + context 'when two deployments with the same track are missing instances' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'mytrack'), + kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 1, track: 'mytrack') + ] + end + + let(:pods) do + [] + end + + it 'returns the correct number of pending pods' do + expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([ + { pod_name: 'Not provided', status: 'pending', track: 'mytrack' }, + { pod_name: 'Not provided', status: 'pending', track: 'mytrack' } + ]) + end + end + + context 'with multiple matching deployments' do + let(:deployments) do + [ + kube_deployment(name: 'deployment-a', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2), + kube_deployment(name: 'deployment-b', environment_slug: environment.slug, project_slug: project.full_path_slug, replicas: 2) + ] + end + + let(:pods) do + [ + kube_pod(name: 'pod-a-1', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-a-2', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-b-1', environment_slug: environment.slug, project_slug: project.full_path_slug), + kube_pod(name: 'pod-b-2', environment_slug: environment.slug, project_slug: project.full_path_slug) + ] + end + + it 'returns each pod once' do + expect(rollout_status.instances.map { |p| p[:pod_name] }).to eq(['pod-a-1', 'pod-a-2', 'pod-b-1', 'pod-b-2']) + end + end + end + + describe '#ingresses' do + subject { service.ingresses(namespace) } + + let(:service) { create(:cluster_platform_kubernetes, :configured) } + let(:namespace) { 'project-namespace' } + + context 'when there is an ingress in the namespace' do + before do + stub_kubeclient_ingresses(namespace) + end + + it 'returns an ingress' do + expect(subject.count).to eq(1) + expect(subject.first).to be_kind_of(::Gitlab::Kubernetes::Ingress) + expect(subject.first.name).to eq('production-auto-deploy') + end + end + + context 'when there are no ingresss in the namespace' do + before do + allow(service.kubeclient).to receive(:get_ingresses) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) } + end + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + + describe '#patch_ingress' do + subject { service.patch_ingress(namespace, ingress, data) } + + let(:service) { create(:cluster_platform_kubernetes, :configured) } + let(:namespace) { 'project-namespace' } + let(:ingress) { Gitlab::Kubernetes::Ingress.new(kube_ingress) } + let(:data) { { metadata: { annotations: { name: 'test' } } } } + + context 'when there is an ingress in the namespace' do + before do + stub_kubeclient_ingresses(namespace, method: :patch, resource_path: "/#{ingress.name}") + end + + it 'returns an ingress' do + expect(subject[:items][0][:metadata][:name]).to eq('production-auto-deploy') + end + end + + context 'when there are no ingresss in the namespace' do + before do + allow(service.kubeclient).to receive(:patch_ingress) { raise Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil) } + end + + it 'raises an error' do + expect { subject }.to raise_error(Kubeclient::ResourceNotFoundError) + end end end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 27c9e62712c..90884bfd0fb 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1421,4 +1421,123 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do end end end + + describe '#rollout_status' do + let!(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) } + let!(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, environment: environment, project: project) } + + subject { environment.rollout_status } + + context 'environment does not have a deployment board available' do + before do + allow(environment).to receive(:has_terminals?).and_return(false) + end + + it { is_expected.to be_nil } + end + + context 'cached rollout status is present' do + let(:pods) { %w(pod1 pod2) } + let(:deployments) { %w(deployment1 deployment2) } + + before do + stub_reactive_cache(environment, pods: pods, deployments: deployments) + end + + it 'fetches the rollout status from the deployment platform' do + expect(environment.deployment_platform).to receive(:rollout_status) + .with(environment, pods: pods, deployments: deployments) + .and_return(:mock_rollout_status) + + is_expected.to eq(:mock_rollout_status) + end + end + + context 'cached rollout status is not present yet' do + before do + stub_reactive_cache(environment, nil) + end + + it 'falls back to a loading status' do + expect(::Gitlab::Kubernetes::RolloutStatus).to receive(:loading).and_return(:mock_loading_status) + + is_expected.to eq(:mock_loading_status) + end + end + end + + describe '#ingresses' do + subject { environment.ingresses } + + let(:deployment_platform) { double(:deployment_platform) } + let(:deployment_namespace) { 'production' } + + before do + allow(environment).to receive(:deployment_platform) { deployment_platform } + allow(environment).to receive(:deployment_namespace) { deployment_namespace } + end + + context 'when rollout status is available' do + before do + allow(environment).to receive(:rollout_status_available?) { true } + end + + it 'fetches ingresses from the deployment platform' do + expect(deployment_platform).to receive(:ingresses).with(deployment_namespace) + + subject + end + end + + context 'when rollout status is not available' do + before do + allow(environment).to receive(:rollout_status_available?) { false } + end + + it 'does nothing' do + expect(deployment_platform).not_to receive(:ingresses) + + subject + end + end + end + + describe '#patch_ingress' do + subject { environment.patch_ingress(ingress, data) } + + let(:ingress) { double(:ingress) } + let(:data) { double(:data) } + let(:deployment_platform) { double(:deployment_platform) } + let(:deployment_namespace) { 'production' } + + before do + allow(environment).to receive(:deployment_platform) { deployment_platform } + allow(environment).to receive(:deployment_namespace) { deployment_namespace } + end + + context 'when rollout status is available' do + before do + allow(environment).to receive(:rollout_status_available?) { true } + end + + it 'fetches ingresses from the deployment platform' do + expect(deployment_platform).to receive(:patch_ingress).with(deployment_namespace, ingress, data) + + subject + end + end + + context 'when rollout status is not available' do + before do + allow(environment).to receive(:rollout_status_available?) { false } + end + + it 'does nothing' do + expect(deployment_platform).not_to receive(:patch_ingress) + + subject + end + end + end end diff --git a/spec/serializers/rollout_status_entity_spec.rb b/spec/serializers/rollout_status_entity_spec.rb new file mode 100644 index 00000000000..7ad4b259bcd --- /dev/null +++ b/spec/serializers/rollout_status_entity_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RolloutStatusEntity do + include KubernetesHelpers + + let(:rollout_status) { kube_deployment_rollout_status } + + let(:entity) do + described_class.new(rollout_status, request: double) + end + + subject { entity.as_json } + + it "exposes status" do + is_expected.to include(:status) + end + + it 'exposes has_legacy_app_label' do + is_expected.to include(:has_legacy_app_label) + end + + context 'when kube deployment is valid' do + it "exposes deployment data" do + is_expected.to include(:instances, :completion, :is_completed) + end + + it 'does not expose canary ingress if it does not exist' do + is_expected.not_to include(:canary_ingress) + end + + context 'when canary ingress exists' do + let(:rollout_status) { kube_deployment_rollout_status(ingresses: [kube_ingress(track: :canary)]) } + + it 'expose canary ingress' do + is_expected.to include(:canary_ingress) + end + end + end + + context 'when kube deployment is empty' do + let(:rollout_status) { empty_deployment_rollout_status } + + it "exposes status" do + is_expected.to include(:status) + end + + it "does not expose deployment data" do + is_expected.not_to include(:instances, :completion, :is_completed, :canary_ingress) + end + end +end diff --git a/spec/serializers/rollout_statuses/ingress_entity_spec.rb b/spec/serializers/rollout_statuses/ingress_entity_spec.rb new file mode 100644 index 00000000000..b87b9e5c6c4 --- /dev/null +++ b/spec/serializers/rollout_statuses/ingress_entity_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RolloutStatuses::IngressEntity do + include KubernetesHelpers + + let(:canary_ingress) { kube_ingress(track: :canary) } + + let(:entity) do + described_class.new(canary_ingress, request: double) + end + + subject { entity.as_json } + + it 'exposes canary weight' do + is_expected.to include(:canary_weight) + end +end -- cgit v1.2.1