Commit 60d07686 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '251113-remove-framework-enum' into 'master'

Remove not null constraint on framework column

See merge request gitlab-org/gitlab!45411
parents f7287b0f 6080b082
# frozen_string_literal: true
class RemoveNotNullConstraintOnFramework < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
GDPR_FRAMEWORK_ID = 1
disable_ddl_transaction!
class TmpComplianceProjectFrameworkSetting < ActiveRecord::Base
self.table_name = 'project_compliance_framework_settings'
self.primary_key = :project_id
include EachBatch
end
def up
change_column_null :project_compliance_framework_settings, :framework, true
end
def down
# Custom frameworks cannot be rolled back easily since we don't have enum for them.
# To make the database consistent, we mark them as GDPR framework.
# Note: framework customization will be implemented in the next 1-3 releases so data
# corruption due to the rollback is unlikely.
TmpComplianceProjectFrameworkSetting.each_batch(of: 100) do |query|
query.where(framework: nil).update_all(framework: GDPR_FRAMEWORK_ID)
end
change_column_null :project_compliance_framework_settings, :framework, false
end
end
234711b96d3869fe826dfd71ae29e0f75e50302bc29a4e60f436ec76b4be3efb
\ No newline at end of file
......@@ -14837,7 +14837,7 @@ ALTER SEQUENCE project_ci_cd_settings_id_seq OWNED BY project_ci_cd_settings.id;
CREATE TABLE project_compliance_framework_settings (
project_id bigint NOT NULL,
framework smallint NOT NULL,
framework smallint,
framework_id bigint,
CONSTRAINT check_d348de9e2d CHECK ((framework_id IS NOT NULL))
);
......
......@@ -2803,7 +2803,7 @@ type ComplianceFramework {
"""
Name of the compliance framework
"""
name: ProjectSettingEnum!
name: String!
}
"""
......@@ -15721,17 +15721,6 @@ type ProjectPermissions {
uploadFile: Boolean!
}
"""
Names of compliance frameworks that can be assigned to a Project
"""
enum ProjectSettingEnum {
gdpr
hipaa
pci_dss
soc_2
sox
}
type ProjectStatistics {
"""
Build artifacts size of the project
......
......@@ -7545,8 +7545,8 @@
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "ProjectSettingEnum",
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
......@@ -45562,47 +45562,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "ProjectSettingEnum",
"description": "Names of compliance frameworks that can be assigned to a Project",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "gdpr",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "hipaa",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pci_dss",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "soc_2",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sox",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ProjectStatistics",
......@@ -436,7 +436,7 @@ Represents a ComplianceFramework associated with a Project.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `name` | ProjectSettingEnum! | Name of the compliance framework |
| `name` | String! | Name of the compliance framework |
### ConfigureSastPayload
......@@ -3681,18 +3681,6 @@ Values for sorting projects.
| `SUCCESS` | |
| `WAITING_FOR_RESOURCE` | |
### ProjectSettingEnum
Names of compliance frameworks that can be assigned to a Project.
| Value | Description |
| ----- | ----------- |
| `gdpr` | |
| `hipaa` | |
| `pci_dss` | |
| `soc_2` | |
| `sox` | |
### RegistryState
State of a Geo registry.
......
......@@ -7,10 +7,13 @@ module Types
graphql_name 'ComplianceFramework'
description 'Represents a ComplianceFramework associated with a Project'
field :name, ComplianceManagement::ProjectSettingEnum,
field :name, GraphQL::STRING_TYPE,
null: false,
description: 'Name of the compliance framework',
method: :framework
description: 'Name of the compliance framework'
def name
object.compliance_management_framework.name
end
end
end
end
# frozen_string_literal: true
module Types
module ComplianceManagement
class ProjectSettingEnum < Types::BaseEnum
description 'Names of compliance frameworks that can be assigned to a Project'
::ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.each do |k|
value(k)
end
end
end
end
......@@ -7,12 +7,12 @@ module ComplianceManagement
module ProjectSettingsHelper
def compliance_framework_options
option_values = compliance_framework_option_values
::ComplianceManagement::ComplianceFramework::FRAMEWORKS.map { |k, _v| [option_values.fetch(k), k] }
::ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.map { |framework| [option_values.fetch(framework.identifier), framework.identifier] }
end
def compliance_framework_checkboxes
::ComplianceManagement::ComplianceFramework::FRAMEWORKS.map do |k, v|
[v, compliance_framework_title_values.fetch(k)]
::ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.map do |framework|
[framework.id, compliance_framework_title_values.fetch(framework.identifier)]
end
end
......
......@@ -253,7 +253,7 @@ module EE
end
def show_compliance_framework_badge?(project)
project&.compliance_framework_setting&.present?
project&.compliance_framework_setting&.compliance_management_framework.present?
end
def scheduled_for_deletion?(project)
......
......@@ -2,40 +2,5 @@
module ComplianceManagement
module ComplianceFramework
FRAMEWORKS = {
gdpr: 1, # General Data Protection Regulation
hipaa: 2, # Health Insurance Portability and Accountability Act
pci_dss: 3, # Payment Card Industry-Data Security Standard
soc_2: 4, # Service Organization Control 2
sox: 5 # Sarbanes-Oxley
}.freeze
ENUM_FRAMEWORK_MAPPING = {
FRAMEWORKS[:gdpr] => {
name: 'GDPR',
description: 'General Data Protection Regulation',
color: '#1aaa55'
}.freeze,
FRAMEWORKS[:hipaa] => {
name: 'HIPAA',
description: 'Health Insurance Portability and Accountability Act',
color: '#1f75cb'
}.freeze,
FRAMEWORKS[:pci_dss] => {
name: 'PCI-DSS',
description: 'Payment Card Industry-Data Security Standard',
color: '#6666c4'
}.freeze,
FRAMEWORKS[:soc_2] => {
name: 'SOC 2',
description: 'Service Organization Control 2',
color: '#dd2b0e'
}.freeze,
FRAMEWORKS[:sox] => {
name: 'SOX',
description: 'Sarbanes-Oxley',
color: '#fc9403'
}.freeze
}.freeze
end
end
......@@ -5,37 +5,17 @@ require_dependency 'compliance_management/compliance_framework'
module ComplianceManagement
module ComplianceFramework
class ProjectSettings < ApplicationRecord
include IgnorableColumns
self.table_name = 'project_compliance_framework_settings'
self.primary_key = :project_id
ignore_columns :framework, remove_after: '2020-12-06', remove_with: '13.7'
belongs_to :project
belongs_to :compliance_management_framework, class_name: "ComplianceManagement::Framework", foreign_key: :framework_id
enum framework: ::ComplianceManagement::ComplianceFramework::FRAMEWORKS
validates :project, presence: true
validates :framework, uniqueness: { scope: [:project_id] }
validates :framework, inclusion: { in: self.frameworks.keys }
before_save :ensure_compliance_framework_record
private
# Temporary callback for compatibility.
# This keeps the ComplianceManagement::Framework table in-sync with the `framework` enum column.
# At a later point the enum column will be removed so we can support custom frameworks.
def ensure_compliance_framework_record
framework_params = ComplianceManagement::ComplianceFramework::ENUM_FRAMEWORK_MAPPING[self.class.frameworks[framework]]
root_namespace = project.namespace.root_ancestor
# Framework is associated with the root group, there could be a case where the framework is already
# there. Using safe_find_or_create_by is not enough because some attributes (color) could be changed on the framework record, however
# the name is unique. For now we try to create the record and rescue RecordNotUnique error.
ComplianceManagement::Framework.create(framework_params.merge(namespace_id: root_namespace.id)) rescue ActiveRecord::RecordNotUnique
# We're sure that the framework record exists.
self.compliance_management_framework = ComplianceManagement::Framework.find_by!(namespace_id: root_namespace.id, name: framework_params[:name])
end
end
end
end
......@@ -4,6 +4,53 @@ module ComplianceManagement
class Framework < ApplicationRecord
include StripAttribute
include IgnorableColumns
include Gitlab::Utils::StrongMemoize
DefaultFramework = Struct.new(:name, :description, :color, :identifier, :id) do
def to_framework_params
to_h.slice(:name, :description, :color)
end
end
DEFAULT_FRAMEWORKS = [
DefaultFramework.new(
'GDPR',
'General Data Protection Regulation',
'#1aaa55',
:gdpr,
1
).freeze,
DefaultFramework.new(
'HIPAA',
'Health Insurance Portability and Accountability Act',
'#1f75cb',
:hipaa,
2
).freeze,
DefaultFramework.new(
'PCI-DSS',
'Payment Card Industry-Data Security Standard',
'#6666c4',
:pci_dss,
3
).freeze,
DefaultFramework.new(
'SOC 2',
'Service Organization Control 2',
'#dd2b0e',
:soc_2,
4
).freeze,
DefaultFramework.new(
'SOX',
'Sarbanes-Oxley',
'#fc9403',
:sox,
5
).freeze
].freeze
DEFAULT_FRAMEWORKS_BY_IDENTIFIER = DEFAULT_FRAMEWORKS.index_by(&:identifier).with_indifferent_access.freeze
self.table_name = 'compliance_management_frameworks'
......@@ -18,5 +65,27 @@ module ComplianceManagement
validates :description, presence: true, length: { maximum: 255 }
validates :color, color: true, allow_blank: false, length: { maximum: 10 }
validates :namespace_id, uniqueness: { scope: :name }
def merge_request_approval_rules_enforced?
return false unless default_framework_definition
::Gitlab::CurrentSettings.current_application_settings.compliance_frameworks.include?(default_framework_definition.id)
end
def default_framework_definition
strong_memoize(:default_framework_definition) do
DEFAULT_FRAMEWORKS.find { |framework| framework.name.eql?(name) }
end
end
def self.find_or_create_legacy_default_framework(project, framework_identifier)
framework_params = ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER.fetch(framework_identifier).to_framework_params
root_namespace = project.root_namespace
# Framework is associated with the root group, there could be a case where the framework is already there.
ComplianceManagement::Framework
.create_with(framework_params)
.safe_find_or_create_by(namespace_id: root_namespace.id, name: framework_params[:name])
end
end
end
......@@ -393,7 +393,7 @@ module EE
end
def allowed_frameworks
if Array.wrap(compliance_frameworks).any? { |value| !::ComplianceManagement::ComplianceFramework::FRAMEWORKS.value?(value) }
if Array.wrap(compliance_frameworks).any? { |value| !::ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.map(&:id).include?(value) }
errors.add(:compliance_frameworks, _('must contain only valid frameworks'))
end
end
......
......@@ -235,10 +235,7 @@ module EE
def has_regulated_settings?
strong_memoize(:has_regulated_settings) do
next false unless compliance_framework_setting
compliance_framework_id = ::ComplianceManagement::ComplianceFramework::FRAMEWORKS[compliance_framework_setting.framework.to_sym]
::Gitlab::CurrentSettings.current_application_settings.compliance_frameworks.include?(compliance_framework_id)
compliance_framework_setting&.compliance_management_framework&.merge_request_approval_rules_enforced?
end
end
......
......@@ -68,12 +68,16 @@ module EE
settings = params[:compliance_framework_setting_attributes]
return unless settings.present?
unless can?(current_user, :admin_compliance_framework, project)
if can?(current_user, :admin_compliance_framework, project)
framework_identifier = settings.delete(:framework)
if framework_identifier.blank?
settings.merge!(_destroy: true)
else
settings[:compliance_management_framework] = ComplianceManagement::Framework.find_or_create_legacy_default_framework(project, framework_identifier)
end
else
params.delete(:compliance_framework_setting_attributes)
return
end
settings.merge!(_destroy: settings[:framework].blank?)
end
def log_audit_events
......
- project = local_assigns.fetch(:project)
- if show_compliance_framework_badge?(project)
- framework = project.compliance_framework_setting.framework
- color = compliance_framework_color(framework)
- title = compliance_framework_title(framework)
- tooltip = compliance_framework_tooltip(framework)
- framework = project.compliance_framework_setting.compliance_management_framework
%span.badge.compliance-framework-pill.ml-2.has-tooltip{ class: color, data: { container: 'body' }, title: tooltip }
= title
%span.badge.compliance-framework-pill.ml-2.has-tooltip{ style: "background-color: #{framework.color}", data: { container: 'body' }, title: framework.description }
= framework.name
......@@ -5,4 +5,5 @@
= f.fields_for :compliance_framework_setting, ComplianceManagement::ComplianceFramework::ProjectSettings.new do |cf|
= cf.label :framework, _('Compliance framework (optional)'), class: 'label-bold'
%p.text-secondary= _('Select required regulatory standard')
= cf.select :framework, options_for_select(compliance_framework_options, @project.compliance_framework_setting&.framework), { selected: '', disabled: '', prompt: _('Choose your framework'), include_blank: _('None') }, class: 'form-control'
- selected_default_framework = @project.compliance_framework_setting&.compliance_management_framework&.default_framework_definition&.identifier
= cf.select :framework, options_for_select(compliance_framework_options, selected_default_framework), { selected: '', disabled: '', prompt: _('Choose your framework'), include_blank: _('None') }, class: 'form-control'
---
title: Remove not null constraint on framework column in project_compliance_framework_settings
table
merge_request: 45411
author:
type: changed
......@@ -29,7 +29,7 @@ module EE
project.marked_for_deletion_at
end
expose :compliance_frameworks do |project, _|
[project.compliance_framework_setting&.framework].compact
[project.compliance_framework_setting&.compliance_management_framework&.name].compact
end
end
end
......
......@@ -384,8 +384,8 @@ RSpec.describe ProjectsController do
end
context 'compliance framework settings' do
let(:framework) { ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.sample }
let(:params) { { compliance_framework_setting_attributes: { framework: framework } } }
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.last }
let(:params) { { compliance_framework_setting_attributes: { framework: framework.identifier } } }
context 'when unlicensed' do
before do
......@@ -419,7 +419,7 @@ RSpec.describe ProjectsController do
}
project.reload
expect(project.compliance_framework_setting.framework).to eq(framework)
expect(project.compliance_framework_setting.compliance_management_framework.name).to eq(framework.name)
end
end
end
......
......@@ -3,11 +3,12 @@
FactoryBot.define do
factory :compliance_framework_project_setting, class: 'ComplianceManagement::ComplianceFramework::ProjectSettings' do
project
framework { ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.sample }
ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.each do |k|
trait k do
framework { k }
gdpr
ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.each do |framework|
trait framework.identifier do
compliance_management_framework { association :compliance_framework, framework.to_framework_params.merge(namespace: project.root_namespace) }
end
end
end
......
......@@ -44,7 +44,7 @@ FactoryBot.modify do
end
trait :with_sox_compliance_framework do
association :compliance_framework_setting, factory: :compliance_framework_project_setting, framework: 'sox'
association :compliance_framework_setting, :sox, factory: :compliance_framework_project_setting
end
end
end
......@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'Admin interacts with merge requests approvals settings' do
include StubENV
let_it_be(:hippa) { ComplianceManagement::ComplianceFramework::FRAMEWORKS[:hipaa] }
let_it_be(:application_settings) { create(:application_setting, compliance_frameworks: [hippa]) }
let_it_be(:hippa) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER[:hipaa] }
let_it_be(:application_settings) { create(:application_setting, compliance_frameworks: [hippa.id]) }
let_it_be(:user) { create(:admin) }
before do
......
......@@ -16,7 +16,10 @@ RSpec.describe Resolvers::ComplianceFrameworksResolver do
end
it 'includes the name of the compliance frameworks' do
expect(subject).to contain_exactly(have_attributes(framework: 'sox'))
expect(subject.size).to eq(1)
framework = subject.first.compliance_management_framework
expect(framework.name).to eq('SOX')
end
end
......
......@@ -16,7 +16,7 @@ RSpec.describe ::EE::API::Entities::Project do
let(:project) { create(:project, :with_sox_compliance_framework) }
it 'is an array containing a single compliance framework' do
expect(subject[:compliance_frameworks]).to contain_exactly('sox')
expect(subject[:compliance_frameworks]).to contain_exactly('SOX')
end
end
......
......@@ -7,8 +7,6 @@ RSpec.describe ComplianceManagement::ComplianceFramework::ProjectSettings do
let_it_be(:sub_group) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: sub_group) }
let(:known_frameworks) { ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys }
subject { build(:compliance_framework_project_setting, project: project) }
describe 'Associations' do
......@@ -21,18 +19,6 @@ RSpec.describe ComplianceManagement::ComplianceFramework::ProjectSettings do
it 'confirms the presence of project' do
expect(subject).to validate_presence_of(:project)
end
it 'confirms that the framework is unique for the project' do
expect(subject).to validate_uniqueness_of(:framework).scoped_to(:project_id).ignoring_case_sensitivity
end
it 'allows all known frameworks' do
expect(subject).to allow_values(*known_frameworks).for(:framework)
end
it 'invalidates an unknown framework' do
expect { build :compliance_framework_project_setting, framework: 'ABCDEFGH' }.to raise_error(ArgumentError).with_message(/is not a valid framework/)
end
end
describe 'creation of ComplianceManagement::Framework record' do
......@@ -41,13 +27,16 @@ RSpec.describe ComplianceManagement::ComplianceFramework::ProjectSettings do
it 'creates a new record' do
expect(subject.reload.compliance_management_framework.name).to eq('SOX')
end
end
describe 'set a custom ComplianceManagement::Framework' do
let(:framework) { create(:compliance_framework, name: 'my framework') }
context 'when the framework record already exists for the group' do
let!(:existing_compliance_framework) { group.compliance_management_frameworks.create!(name: 'SOX', description: 'does not matter', color: '#004494') }
it 'assigns the framework' do
subject.compliance_management_framework = framework
subject.save!
it 'creates a new record' do
expect(subject.reload.compliance_management_framework).to eq(existing_compliance_framework)
end
expect(subject.compliance_management_framework.name).to eq('my framework')
end
end
end
......@@ -24,4 +24,43 @@ RSpec.describe ComplianceManagement::Framework do
end
end
end
describe '.find_or_create_legacy_default_framework' do
let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, group: group) }
let_it_be(:project_2) { create(:project, group: group) }
let_it_be(:sox_framework) { create(:compliance_framework_project_setting, :sox, project: project_1).compliance_management_framework }
shared_examples 'framework sharing on the group level' do
it 'shares the same compliance framework on the group level' do
framework = described_class.find_or_create_legacy_default_framework(project_2, :sox)
expect(framework).to eq(sox_framework)
end
end
it_behaves_like 'framework sharing on the group level'
context 'when not "important" attributes differ' do
before do
sox_framework.update!(color: '#ccc')
end
it_behaves_like 'framework sharing on the group level'
end
context 'when the framework does no exist' do
it 'creates the new framework record' do
expect do
described_class.find_or_create_legacy_default_framework(project_2, :gdpr)
end.to change { ComplianceManagement::Framework.where(namespace: group).count }.from(1).to(2)
end
end
context 'when creating an unknown legacy framework' do
it 'raises error' do
expect { described_class.find_or_create_legacy_default_framework(project_2, :unknown) }.to raise_error(KeyError)
end
end
end
end
......@@ -635,15 +635,15 @@ RSpec.describe Project do
end
describe '#has_regulated_settings?' do
let(:framework) { ComplianceManagement::ComplianceFramework::FRAMEWORKS.first }
let(:compliance_framework_setting) { build(:compliance_framework_project_setting, framework: framework.first.to_s) }
let(:gdpr_framework_definition) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER[:gdpr] }
let(:compliance_framework_setting) { build(:compliance_framework_project_setting, :gdpr) }
let(:project) { build(:project, compliance_framework_setting: compliance_framework_setting) }
subject { project.has_regulated_settings? }
context 'framework is regulated' do
before do
stub_application_setting(compliance_frameworks: [framework.last])
stub_application_setting(compliance_frameworks: [gdpr_framework_definition.id])
end
it { is_expected.to be_truthy }
......
......@@ -33,7 +33,7 @@ RSpec.describe 'getting a compliance frameworks list for a project' do
it 'includes its name' do
post_graphql(query, current_user: current_user)
expect(compliance_frameworks).to contain_exactly('name' => 'sox')
expect(compliance_frameworks).to contain_exactly('name' => 'SOX')
end
end
end
......@@ -151,7 +151,7 @@ RSpec.describe API::Projects do
end
it 'exposes framework names as array of strings' do
expect(json_response['compliance_frameworks']).to contain_exactly('sox')
expect(json_response['compliance_frameworks']).to contain_exactly(project.compliance_framework_setting.compliance_management_framework.name)
end
end
......
......@@ -259,7 +259,7 @@ RSpec.describe Projects::UpdateService, '#execute' do
end
context 'when compliance frameworks is set' do
let(:project_setting) { create(:compliance_framework_project_setting) }
let(:project_setting) { create(:compliance_framework_project_setting, :gdpr) }
before do
stub_licensed_features(compliance_framework: true)
......@@ -267,13 +267,17 @@ RSpec.describe Projects::UpdateService, '#execute' do
end
context 'when framework is not blank' do
let(:framework) { ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.without(project_setting.framework).sample }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework } } }
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER[:hipaa] }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework.identifier } } }
it 'saves the framework' do
update_project(project, user, opts)
expect(project.reload.compliance_framework_setting.framework).to eq(framework)
expect { update_project(project, user, opts) }.to change {
project
.reload
.compliance_framework_setting
.compliance_management_framework
.name
}.from('GDPR').to('HIPAA')
end
end
......@@ -289,6 +293,9 @@ RSpec.describe Projects::UpdateService, '#execute' do
end
context 'when compliance framework feature is disabled' do
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER[:sox] }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework.identifier } } }
before do
stub_licensed_features(compliance_framework: false)
end
......@@ -300,20 +307,12 @@ RSpec.describe Projects::UpdateService, '#execute' do
project.update!(compliance_framework_setting: project_setting)
end
let(:framework) { ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.without(project_setting.framework).sample }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework } } }
it 'does not save the new framework and retains the old setting' do
update_project(project, user, opts)
expect(project.reload.compliance_framework_setting.framework).to eq(project_setting.framework)
expect { update_project(project, user, opts) }.not_to change { framework.name }
end
end
context 'the project never had the feature' do
let(:framework) { ComplianceManagement::ComplianceFramework::ProjectSettings.frameworks.keys.sample }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework } } }
it 'does not save the framework' do
update_project(project, user, opts)
......
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