Commit be7874c2 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'backport-plan-limits' into 'master'

Backport plan limits

See merge request gitlab-org/gitlab!27663
parents fc594fda 45272e9e
...@@ -6,6 +6,10 @@ module Ci ...@@ -6,6 +6,10 @@ module Ci
include Importable include Importable
include StripAttribute include StripAttribute
include Schedulable include Schedulable
include Limitable
self.limit_name = 'ci_pipeline_schedules'
self.limit_scope = :project
belongs_to :project belongs_to :project
belongs_to :owner, class_name: 'User' belongs_to :owner, class_name: 'User'
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
class ProjectHook < WebHook class ProjectHook < WebHook
include TriggerableHooks include TriggerableHooks
include Presentable include Presentable
include Limitable
self.limit_scope = :project
triggerable_hooks [ triggerable_hooks [
:push_hooks, :push_hooks,
......
...@@ -346,6 +346,21 @@ class Namespace < ApplicationRecord ...@@ -346,6 +346,21 @@ class Namespace < ApplicationRecord
.try(name) .try(name)
end end
def actual_plan
Plan.default
end
def actual_limits
# We default to PlanLimits.new otherwise a lot of specs would fail
# On production each plan should already have associated limits record
# https://gitlab.com/gitlab-org/gitlab/issues/36037
actual_plan.limits || PlanLimits.new
end
def actual_plan_name
actual_plan.name
end
private private
def all_projects_with_pages def all_projects_with_pages
......
# frozen_string_literal: true
class Plan < ApplicationRecord
DEFAULT = 'default'.freeze
has_one :limits, class_name: 'PlanLimits'
ALL_PLANS = [DEFAULT].freeze
DEFAULT_PLANS = [DEFAULT].freeze
private_constant :ALL_PLANS, :DEFAULT_PLANS
# This always returns an object
def self.default
Gitlab::SafeRequestStore.fetch(:plan_default) do
# find_by allows us to find object (cheaply) against replica DB
# safe_find_or_create_by does stick to primary DB
find_by(name: DEFAULT) || safe_find_or_create_by(name: DEFAULT)
end
end
def self.all_plans
ALL_PLANS
end
def self.default_plans
DEFAULT_PLANS
end
def default?
self.class.default_plans.include?(name)
end
def paid?
false
end
end
Plan.prepend_if_ee('EE::Plan')
...@@ -357,6 +357,7 @@ class Project < ApplicationRecord ...@@ -357,6 +357,7 @@ class Project < ApplicationRecord
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
......
# frozen_string_literal: true
class AddUniqueIndexOnPlanName < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
remove_concurrent_index :plans, :name
add_concurrent_index :plans, :name, unique: true
end
def down
remove_concurrent_index :plans, :name, unique: true
add_concurrent_index :plans, :name
end
end
...@@ -9872,7 +9872,7 @@ CREATE INDEX index_personal_access_tokens_on_user_id ON public.personal_access_t ...@@ -9872,7 +9872,7 @@ CREATE INDEX index_personal_access_tokens_on_user_id ON public.personal_access_t
CREATE UNIQUE INDEX index_plan_limits_on_plan_id ON public.plan_limits USING btree (plan_id); CREATE UNIQUE INDEX index_plan_limits_on_plan_id ON public.plan_limits USING btree (plan_id);
CREATE INDEX index_plans_on_name ON public.plans USING btree (name); CREATE UNIQUE INDEX index_plans_on_name ON public.plans USING btree (name);
CREATE UNIQUE INDEX index_pool_repositories_on_disk_path ON public.pool_repositories USING btree (disk_path); CREATE UNIQUE INDEX index_pool_repositories_on_disk_path ON public.pool_repositories USING btree (disk_path);
...@@ -13262,6 +13262,7 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -13262,6 +13262,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200413072059 20200413072059
20200413230056 20200413230056
20200414144547 20200414144547
20200415153154
20200415160722 20200415160722
20200415161021 20200415161021
20200415161206 20200415161206
......
...@@ -90,7 +90,7 @@ project.actual_limits.exceeded?(:project_hooks, 10) ...@@ -90,7 +90,7 @@ project.actual_limits.exceeded?(:project_hooks, 10)
#### `Limitable` concern #### `Limitable` concern
The [`Limitable` concern](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/app/models/concerns/limitable.rb) The [`Limitable` concern](https://gitlab.com/gitlab-org/gitlab/blob/master/app/models/concerns/limitable.rb)
can be used to validate that a model does not exceed the limits. It ensures can be used to validate that a model does not exceed the limits. It ensures
that the count of the records for the current model does not exceed the defined that the count of the records for the current model does not exceed the defined
limit. limit.
......
...@@ -7,10 +7,6 @@ module EE ...@@ -7,10 +7,6 @@ module EE
prepended do prepended do
include UsageStatistics include UsageStatistics
include Limitable
self.limit_name = 'ci_pipeline_schedules'
self.limit_scope = :project
end end
end end
end end
......
...@@ -274,7 +274,7 @@ module EE ...@@ -274,7 +274,7 @@ module EE
# We are plucking the user_ids from the "Members" table in an array and # We are plucking the user_ids from the "Members" table in an array and
# converting the array of user_ids to a Set which will have unique user_ids. # converting the array of user_ids to a Set which will have unique user_ids.
def billed_user_ids(requested_hosted_plan = nil) def billed_user_ids(requested_hosted_plan = nil)
if [actual_plan_name, requested_hosted_plan].include?(Plan::GOLD) if [actual_plan_name, requested_hosted_plan].include?(::Plan::GOLD)
strong_memoize(:gold_billed_user_ids) do strong_memoize(:gold_billed_user_ids) do
(billed_group_members.non_guests.distinct.pluck(:user_id) + (billed_group_members.non_guests.distinct.pluck(:user_id) +
billed_project_members.non_guests.distinct.pluck(:user_id) + billed_project_members.non_guests.distinct.pluck(:user_id) +
......
...@@ -11,10 +11,10 @@ module EE ...@@ -11,10 +11,10 @@ module EE
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
NAMESPACE_PLANS_TO_LICENSE_PLANS = { NAMESPACE_PLANS_TO_LICENSE_PLANS = {
Plan::BRONZE => License::STARTER_PLAN, ::Plan::BRONZE => License::STARTER_PLAN,
Plan::SILVER => License::PREMIUM_PLAN, ::Plan::SILVER => License::PREMIUM_PLAN,
Plan::GOLD => License::ULTIMATE_PLAN, ::Plan::GOLD => License::ULTIMATE_PLAN,
Plan::EARLY_ADOPTER => License::EARLY_ADOPTER_PLAN ::Plan::EARLY_ADOPTER => License::EARLY_ADOPTER_PLAN
}.freeze }.freeze
LICENSE_PLANS_TO_NAMESPACE_PLANS = NAMESPACE_PLANS_TO_LICENSE_PLANS.invert.freeze LICENSE_PLANS_TO_NAMESPACE_PLANS = NAMESPACE_PLANS_TO_LICENSE_PLANS.invert.freeze
...@@ -47,7 +47,7 @@ module EE ...@@ -47,7 +47,7 @@ module EE
scope :with_feature_available_in_plan, -> (feature) do scope :with_feature_available_in_plan, -> (feature) do
plans = plans_with_feature(feature) plans = plans_with_feature(feature)
matcher = Plan.where(name: plans) matcher = ::Plan.where(name: plans)
.joins(:hosted_subscriptions) .joins(:hosted_subscriptions)
.where("gitlab_subscriptions.namespace_id = namespaces.id") .where("gitlab_subscriptions.namespace_id = namespaces.id")
.select('1') .select('1')
...@@ -210,30 +210,20 @@ module EE ...@@ -210,30 +210,20 @@ module EE
available_features[feature] available_features[feature]
end end
override :actual_plan
def actual_plan def actual_plan
strong_memoize(:actual_plan) do strong_memoize(:actual_plan) do
if parent_id if parent_id
root_ancestor.actual_plan root_ancestor.actual_plan
else else
subscription = find_or_create_subscription subscription = find_or_create_subscription
subscription&.hosted_plan || Plan.free || Plan.default subscription&.hosted_plan
end end
end end || super
end
def actual_limits
# We default to PlanLimits.new otherwise a lot of specs would fail
# On production each plan should already have associated limits record
# https://gitlab.com/gitlab-org/gitlab/issues/36037
actual_plan&.limits || PlanLimits.new
end
def actual_plan_name
actual_plan&.name || Plan::FREE
end end
def plan_name_for_upgrading def plan_name_for_upgrading
return Plan::FREE if trial_active? return ::Plan::FREE if trial_active?
actual_plan_name actual_plan_name
end end
...@@ -302,9 +292,9 @@ module EE ...@@ -302,9 +292,9 @@ module EE
def plans def plans
@plans ||= @plans ||=
if parent_id if parent_id
Plan.hosted_plans_for_namespaces(self_and_ancestors.select(:id)) ::Plan.hosted_plans_for_namespaces(self_and_ancestors.select(:id))
else else
Plan.hosted_plans_for_namespaces(self) ::Plan.hosted_plans_for_namespaces(self)
end end
end end
...@@ -328,7 +318,7 @@ module EE ...@@ -328,7 +318,7 @@ module EE
::Gitlab.com? && ::Gitlab.com? &&
parent_id.nil? && parent_id.nil? &&
trial_ends_on.blank? && trial_ends_on.blank? &&
[Plan::EARLY_ADOPTER, Plan::FREE].include?(actual_plan_name) [::Plan::EARLY_ADOPTER, ::Plan::FREE].include?(actual_plan_name)
end end
def trial_active? def trial_active?
...@@ -342,7 +332,7 @@ module EE ...@@ -342,7 +332,7 @@ module EE
def trial_expired? def trial_expired?
trial_ends_on.present? && trial_ends_on.present? &&
trial_ends_on < Date.today && trial_ends_on < Date.today &&
actual_plan_name == Plan::FREE actual_plan_name == ::Plan::FREE
end end
# A namespace may not have a file template project # A namespace may not have a file template project
...@@ -362,23 +352,23 @@ module EE ...@@ -362,23 +352,23 @@ module EE
end end
def free_plan? def free_plan?
actual_plan_name == Plan::FREE actual_plan_name == ::Plan::FREE
end end
def early_adopter_plan? def early_adopter_plan?
actual_plan_name == Plan::EARLY_ADOPTER actual_plan_name == ::Plan::EARLY_ADOPTER
end end
def bronze_plan? def bronze_plan?
actual_plan_name == Plan::BRONZE actual_plan_name == ::Plan::BRONZE
end end
def silver_plan? def silver_plan?
actual_plan_name == Plan::SILVER actual_plan_name == ::Plan::SILVER
end end
def gold_plan? def gold_plan?
actual_plan_name == Plan::GOLD actual_plan_name == ::Plan::GOLD
end end
def use_elasticsearch? def use_elasticsearch?
......
# frozen_string_literal: true
module EE
module Plan
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
FREE = 'free'.freeze
BRONZE = 'bronze'.freeze
SILVER = 'silver'.freeze
GOLD = 'gold'.freeze
EARLY_ADOPTER = 'early_adopter'.freeze
EE_DEFAULT_PLANS = (const_get(:DEFAULT_PLANS, false) + [FREE]).freeze
PAID_HOSTED_PLANS = [BRONZE, SILVER, GOLD].freeze
FREE_HOSTED_PLANS = [EARLY_ADOPTER].freeze
EE_ALL_PLANS = (EE_DEFAULT_PLANS + PAID_HOSTED_PLANS + FREE_HOSTED_PLANS).freeze
# This constant must keep ordered by tier.
ALL_HOSTED_PLANS = (PAID_HOSTED_PLANS + FREE_HOSTED_PLANS).freeze
has_many :hosted_subscriptions, class_name: 'GitlabSubscription', foreign_key: 'hosted_plan_id'
EE::Plan.private_constant :EE_ALL_PLANS, :EE_DEFAULT_PLANS
end
class_methods do
extend ::Gitlab::Utils::Override
override :all_plans
def all_plans
EE_ALL_PLANS
end
override :default_plans
def default_plans
EE_DEFAULT_PLANS
end
override :default
def default
# GitLab.com default plan is `free`
if ::Gitlab.com?
free
else
super
end
end
# This always returns an object if running on GitLab.com
def free
return unless ::Gitlab.com?
::Gitlab::SafeRequestStore.fetch(:plan_free) do
# find_by allows us to find object (cheaply) against replica DB
# safe_find_or_create_by does stick to primary DB
find_by(name: FREE) || safe_find_or_create_by(name: FREE)
end
end
def hosted_plans_for_namespaces(namespaces)
namespaces = Array(namespaces)
::Plan
.joins(:hosted_subscriptions)
.where(name: ALL_HOSTED_PLANS)
.where(gitlab_subscriptions: { namespace_id: namespaces })
.distinct
end
end
override :paid?
def paid?
PAID_HOSTED_PLANS.include?(name)
end
end
end
...@@ -168,7 +168,6 @@ module EE ...@@ -168,7 +168,6 @@ module EE
delegate :merge_pipelines_enabled, :merge_pipelines_enabled=, :merge_pipelines_enabled?, :merge_pipelines_were_disabled?, to: :ci_cd_settings delegate :merge_pipelines_enabled, :merge_pipelines_enabled=, :merge_pipelines_enabled?, :merge_pipelines_were_disabled?, to: :ci_cd_settings
delegate :merge_trains_enabled?, to: :ci_cd_settings delegate :merge_trains_enabled?, to: :ci_cd_settings
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
delegate :gitlab_subscription, to: :namespace delegate :gitlab_subscription, to: :namespace
validates :repository_size_limit, validates :repository_size_limit,
......
...@@ -6,9 +6,7 @@ module EE ...@@ -6,9 +6,7 @@ module EE
prepended do prepended do
include CustomModelNaming include CustomModelNaming
include Limitable
self.limit_scope = :project
self.singular_route_key = :hook self.singular_route_key = :hook
end end
end end
......
...@@ -235,7 +235,7 @@ module EE ...@@ -235,7 +235,7 @@ module EE
::Namespace ::Namespace
.from("(#{namespace_union_for_reporter_developer_maintainer_owned}) #{::Namespace.table_name}") .from("(#{namespace_union_for_reporter_developer_maintainer_owned}) #{::Namespace.table_name}")
.include_gitlab_subscription .include_gitlab_subscription
.where(gitlab_subscriptions: { hosted_plan: Plan.where(name: Plan::PAID_HOSTED_PLANS) }) .where(gitlab_subscriptions: { hosted_plan: ::Plan.where(name: Plan::PAID_HOSTED_PLANS) })
.any? .any?
end end
...@@ -243,14 +243,14 @@ module EE ...@@ -243,14 +243,14 @@ module EE
::Namespace ::Namespace
.from("(#{namespace_union_for_owned}) #{::Namespace.table_name}") .from("(#{namespace_union_for_owned}) #{::Namespace.table_name}")
.include_gitlab_subscription .include_gitlab_subscription
.where(gitlab_subscriptions: { hosted_plan: Plan.where(name: Plan::PAID_HOSTED_PLANS) }) .where(gitlab_subscriptions: { hosted_plan: ::Plan.where(name: Plan::PAID_HOSTED_PLANS) })
.any? .any?
end end
def managed_free_namespaces def managed_free_namespaces
manageable_groups manageable_groups
.left_joins(:gitlab_subscription) .left_joins(:gitlab_subscription)
.merge(GitlabSubscription.left_joins(:hosted_plan).where(plans: { name: [nil, *Plan::DEFAULT_PLANS] })) .merge(GitlabSubscription.left_joins(:hosted_plan).where(plans: { name: [nil, *::Plan.default_plans] }))
.order(:name) .order(:name)
end end
......
# frozen_string_literal: true
class Plan < ApplicationRecord
DEFAULT = 'default'.freeze
FREE = 'free'.freeze
BRONZE = 'bronze'.freeze
SILVER = 'silver'.freeze
GOLD = 'gold'.freeze
EARLY_ADOPTER = 'early_adopter'.freeze
# This constant must keep ordered by tier.
PAID_HOSTED_PLANS = [BRONZE, SILVER, GOLD].freeze
DEFAULT_PLANS = [DEFAULT, FREE].freeze
ALL_HOSTED_PLANS = (PAID_HOSTED_PLANS + [EARLY_ADOPTER]).freeze
has_many :hosted_subscriptions, class_name: 'GitlabSubscription', foreign_key: 'hosted_plan_id'
has_one :limits, class_name: 'PlanLimits'
def self.default
Gitlab::SafeRequestStore[:plan_default] ||= find_by(name: DEFAULT)
end
def self.free
return unless Gitlab.com?
Gitlab::SafeRequestStore[:plan_free] ||= find_by(name: FREE)
end
def self.hosted_plans_for_namespaces(namespaces)
namespaces = Array(namespaces)
Plan
.joins(:hosted_subscriptions)
.where(name: ALL_HOSTED_PLANS)
.where(gitlab_subscriptions: { namespace_id: namespaces })
.distinct
end
def default?
DEFAULT_PLANS.include?(name)
end
def paid?
!default?
end
end
...@@ -17,6 +17,8 @@ describe 'User notification dot', :aggregate_failures do ...@@ -17,6 +17,8 @@ describe 'User notification dot', :aggregate_failures do
context 'when ci minutes are below threshold' do context 'when ci minutes are below threshold' do
before do before do
allow(Gitlab).to receive(:com?) { true }
group.update(last_ci_minutes_usage_notification_level: 30, shared_runners_minutes_limit: 10) group.update(last_ci_minutes_usage_notification_level: 30, shared_runners_minutes_limit: 10)
allow_any_instance_of(EE::Namespace).to receive(:shared_runners_remaining_minutes).and_return(2) allow_any_instance_of(EE::Namespace).to receive(:shared_runners_remaining_minutes).and_return(2)
end end
......
...@@ -21,6 +21,7 @@ describe 'Groups > Billing', :js do ...@@ -21,6 +21,7 @@ describe 'Groups > Billing', :js do
stub_full_request("https://customers.gitlab.com/gitlab_plans?plan=#{plan}") stub_full_request("https://customers.gitlab.com/gitlab_plans?plan=#{plan}")
.to_return(status: 200, body: File.new(Rails.root.join('ee/spec/fixtures/gitlab_com_plans.json'))) .to_return(status: 200, body: File.new(Rails.root.join('ee/spec/fixtures/gitlab_com_plans.json')))
allow(Gitlab).to receive(:com?).and_return(true)
stub_application_setting(check_namespace_plan: true) stub_application_setting(check_namespace_plan: true)
group.add_owner(user) group.add_owner(user)
......
...@@ -9,6 +9,10 @@ describe Gitlab::ApplicationContext do ...@@ -9,6 +9,10 @@ describe Gitlab::ApplicationContext do
let(:namespace) { create(:group) } let(:namespace) { create(:group) }
let(:subgroup) { create(:group, parent: namespace) } let(:subgroup) { create(:group, parent: namespace) }
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
def result(context) def result(context)
context.to_lazy_hash.transform_values { |v| v.respond_to?(:call) ? v.call : v } context.to_lazy_hash.transform_values { |v| v.respond_to?(:call) ? v.call : v }
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Ci::PipelineSchedule do
it_behaves_like 'includes Limitable concern' do
subject { build(:ci_pipeline_schedule) }
end
end
...@@ -201,6 +201,10 @@ describe Namespace do ...@@ -201,6 +201,10 @@ describe Namespace do
describe '#actual_plan_name' do describe '#actual_plan_name' do
let(:namespace) { create(:namespace) } let(:namespace) { create(:namespace) }
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
subject { namespace.actual_plan_name } subject { namespace.actual_plan_name }
context 'when DB is read-only' do context 'when DB is read-only' do
...@@ -872,67 +876,71 @@ describe Namespace do ...@@ -872,67 +876,71 @@ describe Namespace do
end end
describe '#actual_plan' do describe '#actual_plan' do
context 'when namespace has a subscription associated' do context 'when namespace does not have a subscription associated' do
before do it 'generates a subscription and returns default plan' do
create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan) expect(namespace.actual_plan).to eq(Plan.default)
end
it 'returns the plan from the subscription' do # This should be revisited after https://gitlab.com/gitlab-org/gitlab/-/issues/214434
expect(namespace.actual_plan).to eq(gold_plan)
expect(namespace.gitlab_subscription).to be_present expect(namespace.gitlab_subscription).to be_present
end end
end end
context 'when namespace does not have a subscription associated' do context 'when running on Gitlab.com' do
it 'generates a subscription without a plan' do before do
expect(namespace.actual_plan).to be_nil allow(Gitlab).to receive(:com?).and_return(true)
expect(namespace.gitlab_subscription).to be_present
end end
context 'when free plan does exist' do context 'when namespace has a subscription associated' do
before do before do
free_plan create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan)
end end
it 'generates a subscription' do it 'returns the plan from the subscription' do
expect(namespace.actual_plan).to eq(free_plan) expect(namespace.actual_plan).to eq(gold_plan)
expect(namespace.gitlab_subscription).to be_present expect(namespace.gitlab_subscription).to be_present
end end
end end
context 'when default plan does exist' do context 'when namespace does not have a subscription associated' do
before do it 'generates a subscription and returns free plan' do
default_plan expect(namespace.actual_plan).to eq(Plan.free)
end
it 'generates a subscription' do
expect(namespace.actual_plan).to eq(default_plan)
expect(namespace.gitlab_subscription).to be_present expect(namespace.gitlab_subscription).to be_present
end end
end
context 'when namespace is a subgroup with a parent' do
let(:subgroup) { create(:namespace, parent: namespace) }
context 'when free plan does exist' do context 'when free plan does exist' do
before do before do
free_plan free_plan
end end
it 'does not generates a subscription' do it 'generates a subscription' do
expect(subgroup.actual_plan).to eq(free_plan) expect(namespace.actual_plan).to eq(free_plan)
expect(subgroup.gitlab_subscription).not_to be_present expect(namespace.gitlab_subscription).to be_present
end end
end end
context 'when namespace has a subscription associated' do context 'when namespace is a subgroup with a parent' do
before do let(:subgroup) { create(:namespace, parent: namespace) }
create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan)
context 'when free plan does exist' do
before do
free_plan
end
it 'does not generates a subscription' do
expect(subgroup.actual_plan).to eq(free_plan)
expect(subgroup.gitlab_subscription).not_to be_present
end
end end
it 'returns the plan from the subscription' do context 'when namespace has a subscription associated' do
expect(subgroup.actual_plan).to eq(gold_plan) before do
expect(subgroup.gitlab_subscription).not_to be_present create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan)
end
it 'returns the plan from the subscription' do
expect(subgroup.actual_plan).to eq(gold_plan)
expect(subgroup.gitlab_subscription).not_to be_present
end
end end
end end
end end
...@@ -940,24 +948,16 @@ describe Namespace do ...@@ -940,24 +948,16 @@ describe Namespace do
end end
describe '#actual_plan_name' do describe '#actual_plan_name' do
context 'when namespace has a subscription associated' do context 'when namespace does not have a subscription associated' do
before do it 'returns default plan' do
create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan) expect(namespace.actual_plan_name).to eq('default')
end
it 'returns an associated plan name' do
expect(namespace.actual_plan_name).to eq 'gold'
end end
end end
context 'when namespace does not have subscription associated' do context 'when running on Gitlab.com' do
it 'returns a free plan name' do before do
expect(namespace.actual_plan_name).to eq 'free' allow(Gitlab).to receive(:com?).and_return(true)
end end
end
context 'when namespace is a subgroup with a parent' do
let(:subgroup) { create(:namespace, parent: namespace) }
context 'when namespace has a subscription associated' do context 'when namespace has a subscription associated' do
before do before do
...@@ -965,13 +965,33 @@ describe Namespace do ...@@ -965,13 +965,33 @@ describe Namespace do
end end
it 'returns an associated plan name' do it 'returns an associated plan name' do
expect(subgroup.actual_plan_name).to eq 'gold' expect(namespace.actual_plan_name).to eq 'gold'
end end
end end
context 'when namespace does not have subscription associated' do context 'when namespace does not have subscription associated' do
it 'returns a free plan name' do it 'returns a free plan name' do
expect(subgroup.actual_plan_name).to eq 'free' expect(namespace.actual_plan_name).to eq 'free'
end
end
context 'when namespace is a subgroup with a parent' do
let(:subgroup) { create(:namespace, parent: namespace) }
context 'when namespace has a subscription associated' do
before do
create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan)
end
it 'returns an associated plan name' do
expect(subgroup.actual_plan_name).to eq 'gold'
end
end
context 'when namespace does not have subscription associated' do
it 'returns a free plan name' do
expect(subgroup.actual_plan_name).to eq 'free'
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe ProjectHook do
it_behaves_like 'includes Limitable concern' do
subject { build(:project_hook, project: create(:project)) }
end
end
...@@ -3,30 +3,10 @@ ...@@ -3,30 +3,10 @@
require 'spec_helper' require 'spec_helper'
describe Plan do describe Plan do
describe '#default?' do
subject { plan.default? }
Plan::DEFAULT_PLANS.each do |plan|
context "when '#{plan}'" do
let(:plan) { build("#{plan}_plan".to_sym) }
it { is_expected.to be_truthy }
end
end
Plan::PAID_HOSTED_PLANS.each do |plan|
context "when '#{plan}'" do
let(:plan) { build("#{plan}_plan".to_sym) }
it { is_expected.to be_falsey }
end
end
end
describe '#paid?' do describe '#paid?' do
subject { plan.paid? } subject { plan.paid? }
Plan::DEFAULT_PLANS.each do |plan| Plan.default_plans.each do |plan|
context "when '#{plan}'" do context "when '#{plan}'" do
let(:plan) { build("#{plan}_plan".to_sym) } let(:plan) { build("#{plan}_plan".to_sym) }
......
...@@ -19,6 +19,10 @@ describe 'layouts/application' do ...@@ -19,6 +19,10 @@ describe 'layouts/application' do
context 'when we show the notification dot' do context 'when we show the notification dot' do
let(:show_notification_dot) { true } let(:show_notification_dot) { true }
before do
allow(Gitlab).to receive(:com?) { true }
end
it 'has the notification dot' do it 'has the notification dot' do
expect(view).to receive(:track_event).with('show_buy_ci_minutes_notification', label: 'free', property: 'user_dropdown') expect(view).to receive(:track_event).with('show_buy_ci_minutes_notification', label: 'free', property: 'user_dropdown')
......
# frozen_string_literal: true # frozen_string_literal: true
# EE-only
FactoryBot.define do FactoryBot.define do
factory :plan_limits do factory :plan_limits do
plan plan
......
# frozen_string_literal: true # frozen_string_literal: true
# EE-only
FactoryBot.define do FactoryBot.define do
factory :plan do factory :plan do
Plan::DEFAULT_PLANS.each do |plan| Plan.all_plans.each do |plan|
factory :"#{plan}_plan" do factory :"#{plan}_plan" do
name { plan } name { plan }
title { name.titleize } title { name.titleize }
initialize_with { Plan.find_or_create_by(name: plan) } initialize_with { Plan.find_or_create_by(name: plan) }
end end
end end
Plan::ALL_HOSTED_PLANS.each do |plan|
factory :"#{plan}_plan" do
name { plan }
title { name.titleize }
end
end
end end
end end
...@@ -17,6 +17,10 @@ describe Ci::PipelineSchedule do ...@@ -17,6 +17,10 @@ describe Ci::PipelineSchedule do
it { is_expected.to respond_to(:description) } it { is_expected.to respond_to(:description) }
it { is_expected.to respond_to(:next_run_at) } it { is_expected.to respond_to(:next_run_at) }
it_behaves_like 'includes Limitable concern' do
subject { build(:ci_pipeline_schedule) }
end
describe 'validations' do describe 'validations' do
it 'does not allow invalid cron patters' do it 'does not allow invalid cron patters' do
pipeline_schedule = build(:ci_pipeline_schedule, cron: '0 0 0 * *') pipeline_schedule = build(:ci_pipeline_schedule, cron: '0 0 0 * *')
......
...@@ -11,6 +11,10 @@ describe ProjectHook do ...@@ -11,6 +11,10 @@ describe ProjectHook do
it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:project) }
end end
it_behaves_like 'includes Limitable concern' do
subject { build(:project_hook, project: create(:project)) }
end
describe '.push_hooks' do describe '.push_hooks' do
it 'returns hooks for push events only' do it 'returns hooks for push events only' do
hook = create(:project_hook, push_events: true) hook = create(:project_hook, push_events: true)
......
# frozen_string_literal: true
require 'spec_helper'
describe Plan do
describe '#default?' do
subject { plan.default? }
Plan.default_plans.each do |plan|
context "when '#{plan}'" do
let(:plan) { build("#{plan}_plan".to_sym) }
it { is_expected.to be_truthy }
end
end
end
end
...@@ -3,12 +3,14 @@ ...@@ -3,12 +3,14 @@
RSpec.shared_examples 'a pages cronjob scheduling jobs with context' do |scheduled_worker_class| RSpec.shared_examples 'a pages cronjob scheduling jobs with context' do |scheduled_worker_class|
let(:worker) { described_class.new } let(:worker) { described_class.new }
it 'does not cause extra queries for multiple domains' do context 'with RequestStore enabled', :request_store do
control = ActiveRecord::QueryRecorder.new { worker.perform } it 'does not cause extra queries for multiple domains' do
control = ActiveRecord::QueryRecorder.new { worker.perform }
extra_domain extra_domain
expect { worker.perform }.not_to exceed_query_limit(control) expect { worker.perform }.not_to exceed_query_limit(control)
end
end end
it 'schedules the renewal with a context' do it 'schedules the renewal with a context' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment