summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-07 09:08:57 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-07 09:08:57 +0000
commitc417764f00abaa5d2224a50b8d43a15e40ef8790 (patch)
treede2134eec07b27df1770fad10bcd6aa3a52d8f90 /spec
parentcceb99c072e1eac3f144b479ea5909384fa39e7f (diff)
downloadgitlab-ce-c417764f00abaa5d2224a50b8d43a15e40ef8790.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/concerns/controller_with_feature_category/config_spec.rb53
-rw-r--r--spec/controllers/concerns/controller_with_feature_category_spec.rb66
-rw-r--r--spec/controllers/every_controller_spec.rb82
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_spec.js9
-rw-r--r--spec/frontend/alert_management/components/alert_sidebar_status_spec.js107
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js (renamed from spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js)2
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js (renamed from spec/frontend/alert_management/components/alert_sidebar_spec.js)2
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js129
-rw-r--r--spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js (renamed from spec/frontend/alert_management/components/alert_management_system_note_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js35
-rw-r--r--spec/lib/gitlab/class_attributes_spec.rb41
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb14
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb11
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb32
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb22
-rw-r--r--spec/models/merge_request_spec.rb2
-rw-r--r--spec/presenters/alert_management/alert_presenter_spec.rb5
17 files changed, 465 insertions, 149 deletions
diff --git a/spec/controllers/concerns/controller_with_feature_category/config_spec.rb b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
new file mode 100644
index 00000000000..9b8ffd2baab
--- /dev/null
+++ b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require "fast_spec_helper"
+require "rspec-parameterized"
+require_relative "../../../../app/controllers/concerns/controller_with_feature_category/config"
+
+RSpec.describe ControllerWithFeatureCategory::Config do
+ describe "#matches?" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:only_actions, :except_actions, :if_proc, :unless_proc, :test_action, :expected) do
+ nil | nil | nil | nil | "action" | true
+ [:included] | nil | nil | nil | "action" | false
+ [:included] | nil | nil | nil | "included" | true
+ nil | [:excluded] | nil | nil | "excluded" | false
+ nil | nil | true | nil | "action" | true
+ [:included] | nil | true | nil | "action" | false
+ [:included] | nil | true | nil | "included" | true
+ nil | [:excluded] | true | nil | "excluded" | false
+ nil | nil | false | nil | "action" | false
+ [:included] | nil | false | nil | "action" | false
+ [:included] | nil | false | nil | "included" | false
+ nil | [:excluded] | false | nil | "excluded" | false
+ nil | nil | nil | true | "action" | false
+ [:included] | nil | nil | true | "action" | false
+ [:included] | nil | nil | true | "included" | false
+ nil | [:excluded] | nil | true | "excluded" | false
+ nil | nil | nil | false | "action" | true
+ [:included] | nil | nil | false | "action" | false
+ [:included] | nil | nil | false | "included" | true
+ nil | [:excluded] | nil | false | "excluded" | false
+ nil | nil | true | false | "action" | true
+ [:included] | nil | true | false | "action" | false
+ [:included] | nil | true | false | "included" | true
+ nil | [:excluded] | true | false | "excluded" | false
+ nil | nil | false | true | "action" | false
+ [:included] | nil | false | true | "action" | false
+ [:included] | nil | false | true | "included" | false
+ nil | [:excluded] | false | true | "excluded" | false
+ end
+
+ with_them do
+ let(:config) do
+ if_to_proc = if_proc.nil? ? nil : -> (_) { if_proc }
+ unless_to_proc = unless_proc.nil? ? nil : -> (_) { unless_proc }
+
+ described_class.new(:category, only_actions, except_actions, if_to_proc, unless_to_proc)
+ end
+
+ specify { expect(config.matches?(test_action)).to be(expected) }
+ end
+ end
+end
diff --git a/spec/controllers/concerns/controller_with_feature_category_spec.rb b/spec/controllers/concerns/controller_with_feature_category_spec.rb
new file mode 100644
index 00000000000..e603a7d14c4
--- /dev/null
+++ b/spec/controllers/concerns/controller_with_feature_category_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative "../../../app/controllers/concerns/controller_with_feature_category"
+require_relative "../../../app/controllers/concerns/controller_with_feature_category/config"
+
+RSpec.describe ControllerWithFeatureCategory do
+ describe ".feature_category_for_action" do
+ let(:base_controller) do
+ Class.new do
+ include ControllerWithFeatureCategory
+ end
+ end
+
+ let(:controller) do
+ Class.new(base_controller) do
+ feature_category :baz
+ feature_category :foo, except: %w(update edit)
+ feature_category :bar, only: %w(index show)
+ feature_category :quux, only: %w(destroy)
+ feature_category :quuz, only: %w(destroy)
+ end
+ end
+
+ let(:subclass) do
+ Class.new(controller) do
+ feature_category :qux, only: %w(index)
+ end
+ end
+
+ it "is nil when nothing was defined" do
+ expect(base_controller.feature_category_for_action("hello")).to be_nil
+ end
+
+ it "returns the expected category", :aggregate_failures do
+ expect(controller.feature_category_for_action("update")).to eq(:baz)
+ expect(controller.feature_category_for_action("hello")).to eq(:foo)
+ expect(controller.feature_category_for_action("index")).to eq(:bar)
+ end
+
+ it "returns the closest match for categories defined in subclasses" do
+ expect(subclass.feature_category_for_action("index")).to eq(:qux)
+ expect(subclass.feature_category_for_action("show")).to eq(:bar)
+ end
+
+ it "returns the last defined feature category when multiple match" do
+ expect(controller.feature_category_for_action("destroy")).to eq(:quuz)
+ end
+
+ it "raises an error when using including and excluding the same action" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, only: [:world], except: [:world]
+ end
+ end.to raise_error(%r(cannot configure both `only` and `except`))
+ end
+
+ it "raises an error when using unknown arguments" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, hello: :world
+ end
+ end.to raise_error(%r(unknown arguments))
+ end
+ end
+end
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
new file mode 100644
index 00000000000..4785ee9ed8f
--- /dev/null
+++ b/spec/controllers/every_controller_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "Every controller" do
+ context "feature categories" do
+ let_it_be(:feature_categories) do
+ YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:to_sym).to_set
+ end
+
+ let_it_be(:controller_actions) do
+ # This will return tuples of all controller actions defined in the routes
+ # Only for controllers inheriting ApplicationController
+ # Excluding controllers from gems (OAuth, Sidekiq)
+ Rails.application.routes.routes
+ .map { |route| route.required_defaults.presence }
+ .compact
+ .select { |route| route[:controller].present? && route[:action].present? }
+ .map { |route| [constantize_controller(route[:controller]), route[:action]] }
+ .reject { |route| route.first.nil? || !route.first.include?(ControllerWithFeatureCategory) }
+ end
+
+ let_it_be(:routes_without_category) do
+ controller_actions.map do |controller, action|
+ "#{controller}##{action}" unless controller.feature_category_for_action(action)
+ end.compact
+ end
+
+ it "has feature categories" do
+ pending("We'll work on defining categories for all controllers: "\
+ "https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/463")
+
+ expect(routes_without_category).to be_empty, "#{routes_without_category.first(10)} did not have a category"
+ end
+
+ it "completed controllers don't get new routes without categories" do
+ completed_controllers = [Projects::MergeRequestsController].map(&:to_s)
+
+ newly_introduced_missing_category = routes_without_category.select do |route|
+ completed_controllers.any? { |controller| route.start_with?(controller) }
+ end
+
+ expect(newly_introduced_missing_category).to be_empty
+ end
+
+ it "recognizes the feature categories" do
+ routes_unknown_category = controller_actions.map do |controller, action|
+ used_category = controller.feature_category_for_action(action)
+ next unless used_category
+ next if used_category == :not_owned
+
+ ["#{controller}##{action}", used_category] unless feature_categories.include?(used_category)
+ end.compact
+
+ expect(routes_unknown_category).to be_empty, "#{routes_unknown_category.first(10)} had an unknown category"
+ end
+
+ it "doesn't define or exclude categories on removed actions", :aggregate_failures do
+ controller_actions.group_by(&:first).each do |controller, controller_action|
+ existing_actions = controller_action.map(&:last)
+ used_actions = actions_defined_in_feature_category_config(controller)
+ non_existing_used_actions = used_actions - existing_actions
+
+ expect(non_existing_used_actions).to be_empty,
+ "#{controller} used #{non_existing_used_actions} to define feature category, but the route does not exist"
+ end
+ end
+ end
+
+ def constantize_controller(name)
+ "#{name.camelize}Controller".constantize
+ rescue NameError
+ nil # some controllers, like the omniauth ones are dynamic
+ end
+
+ def actions_defined_in_feature_category_config(controller)
+ feature_category_configs = controller.send(:class_attributes)[:feature_category_config]
+ feature_category_configs.map do |config|
+ Array(config.send(:only)) + Array(config.send(:except))
+ end.flatten.uniq.map(&:to_s)
+ end
+end
diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_list_spec.js
index ae994252657..ae95873cb1c 100644
--- a/spec/frontend/alert_management/components/alert_management_list_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_list_spec.js
@@ -207,6 +207,15 @@ describe('AlertManagementList', () => {
expect(findStatusDropdown().exists()).toBe(true);
});
+ it('does not display a dropdown status header', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ expect(findStatusDropdown().contains('.dropdown-title')).toBe(false);
+ });
+
it('shows correct severity icons', () => {
mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
diff --git a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/alert_sidebar_status_spec.js
deleted file mode 100644
index 1c651bbacdc..00000000000
--- a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
-import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
-import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
-import Tracking from '~/tracking';
-import mockAlerts from '../mocks/alerts.json';
-
-const mockAlert = mockAlerts[0];
-
-describe('Alert Details Sidebar Status', () => {
- let wrapper;
- const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
- const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
- wrapper = mount(AlertSidebarStatus, {
- propsData: {
- alert: { ...mockAlert },
- ...data,
- sidebarCollapsed,
- projectPath: 'projectPath',
- },
- mocks: {
- $apollo: {
- mutate: jest.fn(),
- queries: {
- alert: {
- loading,
- },
- },
- },
- },
- stubs,
- });
- }
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('updating the alert status', () => {
- const mockUpdatedMutationResult = {
- data: {
- updateAlertStatus: {
- errors: [],
- alert: {
- status: 'acknowledged',
- },
- },
- },
- };
-
- beforeEach(() => {
- mountComponent({
- data: { alert: mockAlert },
- sidebarCollapsed: false,
- loading: false,
- });
- });
-
- it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
- findStatusDropdownItem().vm.$emit('click');
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: updateAlertStatus,
- variables: {
- iid: '1527542',
- status: 'TRIGGERED',
- projectPath: 'projectPath',
- },
- });
- });
-
- it('stops updating when the request fails', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
- findStatusDropdownItem().vm.$emit('click');
- expect(findStatusLoadingIcon().exists()).toBe(false);
- expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
- });
- });
-
- describe('Snowplow tracking', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alert: mockAlert },
- loading: false,
- });
- });
-
- it('should track alert status updates', () => {
- Tracking.event.mockClear();
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
- findStatusDropdownItem().vm.$emit('click');
- const status = findStatusDropdownItem().text();
- setImmediate(() => {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
- });
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
index 7969f553b29..db086782424 100644
--- a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js
+++ b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
@@ -5,7 +5,7 @@ import { GlDropdownItem } from '@gitlab/ui';
import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue';
import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql';
-import mockAlerts from '../mocks/alerts.json';
+import mockAlerts from '../../mocks/alerts.json';
const mockAlert = mockAlerts[0];
diff --git a/spec/frontend/alert_management/components/alert_sidebar_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
index 2536e0c230a..5235ae63fee 100644
--- a/spec/frontend/alert_management/components/alert_sidebar_spec.js
+++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
@@ -3,7 +3,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import AlertSidebar from '~/alert_management/components/alert_sidebar.vue';
import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import mockAlerts from '../mocks/alerts.json';
+import mockAlerts from '../../mocks/alerts.json';
const mockAlert = mockAlerts[0];
diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
new file mode 100644
index 00000000000..c2eaf540e9c
--- /dev/null
+++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
@@ -0,0 +1,129 @@
+import { mount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
+import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
+import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
+import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
+import Tracking from '~/tracking';
+import mockAlerts from '../../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar Status', () => {
+ let wrapper;
+ const findStatusDropdown = () => wrapper.find(GlDropdown);
+ const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
+ const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
+ wrapper = mount(AlertSidebarStatus, {
+ propsData: {
+ alert: { ...mockAlert },
+ ...data,
+ sidebarCollapsed,
+ projectPath: 'projectPath',
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ queries: {
+ alert: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Alert Sidebar Dropdown Status', () => {
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('displays status dropdown', () => {
+ expect(findStatusDropdown().exists()).toBe(true);
+ });
+
+ it('displays the dropdown status header', () => {
+ expect(findStatusDropdown().contains('.dropdown-title')).toBe(true);
+ });
+
+ describe('updating the alert status', () => {
+ const mockUpdatedMutationResult = {
+ data: {
+ updateAlertStatus: {
+ errors: [],
+ alert: {
+ status: 'acknowledged',
+ },
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+ findStatusDropdownItem().vm.$emit('click');
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: updateAlertStatus,
+ variables: {
+ iid: '1527542',
+ status: 'TRIGGERED',
+ projectPath: 'projectPath',
+ },
+ });
+ });
+
+ it('stops updating when the request fails', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
+ findStatusDropdownItem().vm.$emit('click');
+ expect(findStatusLoadingIcon().exists()).toBe(false);
+ expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
+ });
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alert: mockAlert },
+ loading: false,
+ });
+ });
+
+ it('should track alert status updates', () => {
+ Tracking.event.mockClear();
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
+ findStatusDropdownItem().vm.$emit('click');
+ const status = findStatusDropdownItem().text();
+ setImmediate(() => {
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, {
+ label,
+ property: status,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_system_note_spec.js b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
index 87dc36cc7cb..652cbc4b838 100644
--- a/spec/frontend/alert_management/components/alert_management_system_note_spec.js
+++ b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SystemNote from '~/alert_management/components/system_notes/system_note.vue';
-import mockAlerts from '../mocks/alerts.json';
+import mockAlerts from '../../mocks/alerts.json';
const mockAlert = mockAlerts[1];
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index 2c7fce714f0..16aa8379f56 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -4,7 +4,6 @@ import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
import Icon from '~/vue_shared/components/icon.vue';
const DEFAULT_PROPS = {
- loaded: true,
user: {
username: 'root',
name: 'Administrator',
@@ -12,6 +11,7 @@ const DEFAULT_PROPS = {
bio: null,
workInformation: null,
status: null,
+ loaded: true,
},
};
@@ -46,28 +46,21 @@ describe('User Popover Component', () => {
});
};
- describe('Empty', () => {
- beforeEach(() => {
- createWrapper(
- {},
- {
- propsData: {
- target: findTarget(),
- user: {
- name: null,
- username: null,
- location: null,
- bio: null,
- workInformation: null,
- status: null,
- },
- },
+ describe('when user is loading', () => {
+ it('displays skeleton loaders', () => {
+ createWrapper({
+ user: {
+ name: null,
+ username: null,
+ location: null,
+ bio: null,
+ workInformation: null,
+ status: null,
+ loaded: false,
},
- );
- });
+ });
- it('should return skeleton loaders', () => {
- expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
+ expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(4);
});
});
diff --git a/spec/lib/gitlab/class_attributes_spec.rb b/spec/lib/gitlab/class_attributes_spec.rb
new file mode 100644
index 00000000000..f8766f20495
--- /dev/null
+++ b/spec/lib/gitlab/class_attributes_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::ClassAttributes do
+ let(:klass) do
+ Class.new do
+ include Gitlab::ClassAttributes
+
+ def self.get_attribute(name)
+ get_class_attribute(name)
+ end
+
+ def self.set_attribute(name, value)
+ class_attributes[name] = value
+ end
+ end
+ end
+
+ let(:subclass) { Class.new(klass) }
+
+ describe ".get_class_attribute" do
+ it "returns values set on the class" do
+ klass.set_attribute(:foo, :bar)
+
+ expect(klass.get_attribute(:foo)).to eq(:bar)
+ end
+
+ it "returns values set on a superclass" do
+ klass.set_attribute(:foo, :bar)
+
+ expect(subclass.get_attribute(:foo)).to eq(:bar)
+ end
+
+ it "returns values from the subclass over attributes from a superclass" do
+ klass.set_attribute(:foo, :baz)
+ subclass.set_attribute(:foo, :bar)
+
+ expect(subclass.get_attribute(:foo)).to eq(:bar)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 9d6e80d12af..3508d02838c 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -167,13 +167,13 @@ RSpec.describe Gitlab::Danger::Helper do
describe '#categories_for_file' do
where(:path, :expected_categories) do
- 'doc/foo' | [:none]
- 'CONTRIBUTING.md' | [:none]
- 'LICENSE' | [:none]
- 'MAINTENANCE.md' | [:none]
- 'PHILOSOPHY.md' | [:none]
- 'PROCESS.md' | [:none]
- 'README.md' | [:none]
+ 'doc/foo' | [:docs]
+ 'CONTRIBUTING.md' | [:docs]
+ 'LICENSE' | [:docs]
+ 'MAINTENANCE.md' | [:docs]
+ 'PHILOSOPHY.md' | [:docs]
+ 'PROCESS.md' | [:docs]
+ 'README.md' | [:docs]
'ee/doc/foo' | [:unknown]
'ee/README' | [:unknown]
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index e4306806e0e..640bbebf0da 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -9,7 +9,16 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do
describe '#label' do
it 'returns labels based on class name' do
- expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform')
+ expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform', feature_category: '')
+ end
+
+ it 'contains only the labels defined for metrics' do
+ expect(subject.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
+ end
+
+ it 'includes the feature category if there is one' do
+ expect(test_worker_class).to receive(:get_feature_category).and_return('source_code_management')
+ expect(subject.labels).to include(feature_category: 'source_code_management')
end
end
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 6ceba186660..12e98089066 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -70,6 +70,9 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
describe '#labels' do
+ let(:request) { double(:request, format: double(:format, ref: :html)) }
+ let(:controller_class) { double(:controller_class, name: 'TestController') }
+
context 'when request goes to Grape endpoint' do
before do
route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
@@ -77,8 +80,13 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
env['api.endpoint'] = endpoint
end
+
it 'provides labels with the method and path of the route in the grape endpoint' do
- expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive' })
+ expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive', feature_category: '' })
+ end
+
+ it 'contains only the labels defined for transactions' do
+ expect(transaction.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
end
it 'does not provide labels if route infos are missing' do
@@ -92,24 +100,25 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
context 'when request goes to ActionController' do
- let(:request) { double(:request, format: double(:format, ref: :html)) }
-
before do
- klass = double(:klass, name: 'TestController')
- controller = double(:controller, class: klass, action_name: 'show', request: request)
+ controller = double(:controller, class: controller_class, action_name: 'show', request: request)
env['action_controller.instance'] = controller
end
it 'tags a transaction with the name and action of a controller' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ end
+
+ it 'contains only the labels defined for transactions' do
+ expect(transaction.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
end
context 'when the request content type is not :html' do
let(:request) { double(:request, format: double(:format, ref: :json)) }
it 'appends the mime type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: '' })
end
end
@@ -117,7 +126,14 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: 'http://example.com')) }
it 'does not append the MIME type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ end
+ end
+
+ context 'when the feature category is known' do
+ it 'includes it in the feature category label' do
+ expect(controller_class).to receive(:feature_category_for_action).with('show').and_return(:source_code_management)
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: "source_code_management" })
end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index be761060b1b..cad99beaf20 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -93,6 +93,28 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
+ context 'for monitor' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user, dashboard: 'operations')
+ cluster = create(:cluster, user: user)
+ create(:project, creator: user)
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:monitor]).to include(
+ clusters: 2,
+ clusters_applications_prometheus: 2,
+ operations_dashboard_default_dashboard: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:monitor]).to include(
+ clusters: 1,
+ clusters_applications_prometheus: 1,
+ operations_dashboard_default_dashboard: 1
+ )
+ end
+ end
+
it 'ensures recorded_at is set before any other usage data calculation' do
%i(alt_usage_data redis_usage_data distinct_count count).each do |method|
expect(described_class).not_to receive(method)
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 4853fc2438e..2852a86eeaf 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -12,6 +12,8 @@ RSpec.describe MergeRequest do
subject { create(:merge_request) }
describe 'associations' do
+ subject { build_stubbed(:merge_request) }
+
it { is_expected.to belong_to(:target_project).class_name('Project') }
it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") }
diff --git a/spec/presenters/alert_management/alert_presenter_spec.rb b/spec/presenters/alert_management/alert_presenter_spec.rb
index 9a92048c487..b1bf7029f3e 100644
--- a/spec/presenters/alert_management/alert_presenter_spec.rb
+++ b/spec/presenters/alert_management/alert_presenter_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe AlertManagement::AlertPresenter do
}
end
let_it_be(:alert) do
- create(:alert_management_alert, :with_host, :with_service, :with_monitoring_tool, project: project, payload: generic_payload)
+ create(:alert_management_alert, :with_description, :with_host, :with_service, :with_monitoring_tool, project: project, payload: generic_payload)
end
subject(:presenter) { described_class.new(alert) }
@@ -29,7 +29,8 @@ RSpec.describe AlertManagement::AlertPresenter do
**Severity:** #{presenter.severity}#{markdown_line_break}
**Service:** #{alert.service}#{markdown_line_break}
**Monitoring tool:** #{alert.monitoring_tool}#{markdown_line_break}
- **Hosts:** #{alert.hosts.join(' ')}
+ **Hosts:** #{alert.hosts.join(' ')}#{markdown_line_break}
+ **Description:** #{alert.description}
#### Alert Details