chore: improve plan-based feature handling with plan hierarchy (#11335)
- Refactor HandleStripeEventService to better manage features by plan - Add constants for features available in each plan tier (Startup, Business, Enterprise) - Add channel_instagram to Startup plan features - Improve downgrade handling to properly disable higher-tier features - Clean up and optimize tests for maintainability - Add comprehensive test coverage for plan upgrades and downgrades --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -9,120 +9,312 @@ describe Enterprise::Billing::HandleStripeEventService do
|
||||
let!(:account) { create(:account, custom_attributes: { stripe_customer_id: 'cus_123' }) }
|
||||
|
||||
before do
|
||||
# Create cloud plans configuration
|
||||
create(:installation_config, {
|
||||
name: 'CHATWOOT_CLOUD_PLANS',
|
||||
value: [
|
||||
{ 'name' => 'Hacker', 'product_id' => ['plan_id_hacker'], 'price_ids' => ['price_hacker'] },
|
||||
{ 'name' => 'Startups', 'product_id' => ['plan_id_startups'], 'price_ids' => ['price_startups'] },
|
||||
{ 'name' => 'Business', 'product_id' => ['plan_id_business'], 'price_ids' => ['price_business'] },
|
||||
{ 'name' => 'Enterprise', 'product_id' => ['plan_id_enterprise'], 'price_ids' => ['price_enterprise'] }
|
||||
]
|
||||
})
|
||||
# Setup common subscription mocks
|
||||
allow(event).to receive(:data).and_return(data)
|
||||
allow(data).to receive(:object).and_return(subscription)
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({
|
||||
'id' => 'test', 'product' => 'plan_id', 'name' => 'plan_name'
|
||||
})
|
||||
allow(subscription).to receive(:[]).with('quantity').and_return('10')
|
||||
allow(subscription).to receive(:[]).with('status').and_return('active')
|
||||
allow(subscription).to receive(:[]).with('current_period_end').and_return(1_686_567_520)
|
||||
allow(subscription).to receive(:customer).and_return('cus_123')
|
||||
create(:installation_config, {
|
||||
name: 'CHATWOOT_CLOUD_PLANS',
|
||||
value: [
|
||||
{
|
||||
'name' => 'Hacker',
|
||||
'product_id' => ['plan_id'],
|
||||
'price_ids' => ['price_1']
|
||||
},
|
||||
{
|
||||
'name' => 'Startups',
|
||||
'product_id' => ['plan_id_2'],
|
||||
'price_ids' => ['price_2']
|
||||
}
|
||||
]
|
||||
})
|
||||
allow(event).to receive(:type).and_return('customer.subscription.updated')
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when it gets customer.subscription.updated event' do
|
||||
it 'updates subscription attributes' do
|
||||
allow(event).to receive(:type).and_return('customer.subscription.updated')
|
||||
allow(subscription).to receive(:customer).and_return('cus_123')
|
||||
stripe_event_service.new.perform(event: event)
|
||||
describe 'subscription update handling' do
|
||||
it 'updates account attributes and disables premium features for default plan' do
|
||||
# Setup for default (Hacker) plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_hacker', 'name' => 'Hacker' })
|
||||
|
||||
expect(account.reload.custom_attributes).to eq({
|
||||
'captain_responses_usage' => 0,
|
||||
'stripe_customer_id' => 'cus_123',
|
||||
'stripe_price_id' => 'test',
|
||||
'stripe_product_id' => 'plan_id',
|
||||
'plan_name' => 'Hacker',
|
||||
'subscribed_quantity' => '10',
|
||||
'subscription_ends_on' => Time.zone.at(1_686_567_520).as_json,
|
||||
'subscription_status' => 'active'
|
||||
})
|
||||
end
|
||||
|
||||
it 'resets captain usage' do
|
||||
5.times { account.increment_response_usage }
|
||||
expect(account.custom_attributes['captain_responses_usage']).to eq(5)
|
||||
|
||||
allow(event).to receive(:type).and_return('customer.subscription.updated')
|
||||
allow(subscription).to receive(:customer).and_return('cus_123')
|
||||
stripe_event_service.new.perform(event: event)
|
||||
|
||||
expect(account.reload.custom_attributes['captain_responses_usage']).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
it 'disable features on customer.subscription.updated for default plan' do
|
||||
allow(event).to receive(:type).and_return('customer.subscription.updated')
|
||||
allow(subscription).to receive(:customer).and_return('cus_123')
|
||||
stripe_event_service.new.perform(event: event)
|
||||
expect(account.reload.custom_attributes).to eq({
|
||||
'captain_responses_usage' => 0,
|
||||
'stripe_customer_id' => 'cus_123',
|
||||
'stripe_price_id' => 'test',
|
||||
'stripe_product_id' => 'plan_id',
|
||||
'plan_name' => 'Hacker',
|
||||
'subscribed_quantity' => '10',
|
||||
'subscription_ends_on' => Time.zone.at(1_686_567_520).as_json,
|
||||
'subscription_status' => 'active'
|
||||
})
|
||||
|
||||
# Verify account attributes were updated
|
||||
expect(account.reload.custom_attributes).to include(
|
||||
'plan_name' => 'Hacker',
|
||||
'stripe_product_id' => 'plan_id_hacker',
|
||||
'subscription_status' => 'active'
|
||||
)
|
||||
|
||||
# Verify premium features are disabled for default plan
|
||||
expect(account).not_to be_feature_enabled('channel_email')
|
||||
expect(account).not_to be_feature_enabled('help_center')
|
||||
expect(account).not_to be_feature_enabled('sla')
|
||||
expect(account).not_to be_feature_enabled('custom_roles')
|
||||
expect(account).not_to be_feature_enabled('audit_logs')
|
||||
end
|
||||
|
||||
it 'handles customer.subscription.deleted' do
|
||||
stripe_customer_service = double
|
||||
allow(event).to receive(:type).and_return('customer.subscription.deleted')
|
||||
allow(Enterprise::Billing::CreateStripeCustomerService).to receive(:new).and_return(stripe_customer_service)
|
||||
allow(stripe_customer_service).to receive(:perform)
|
||||
it 'resets captain usage on subscription update' do
|
||||
# Prime the account with some usage
|
||||
5.times { account.increment_response_usage }
|
||||
expect(account.custom_attributes['captain_responses_usage']).to eq(5)
|
||||
|
||||
# Setup for any plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' })
|
||||
|
||||
stripe_event_service.new.perform(event: event)
|
||||
expect(Enterprise::Billing::CreateStripeCustomerService).to have_received(:new).with(account: account)
|
||||
|
||||
# Verify usage was reset
|
||||
expect(account.reload.custom_attributes['captain_responses_usage']).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform for Startups plan' do
|
||||
before do
|
||||
allow(event).to receive(:data).and_return(data)
|
||||
allow(data).to receive(:object).and_return(subscription)
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({
|
||||
'id' => 'test', 'product' => 'plan_id_2', 'name' => 'plan_name'
|
||||
})
|
||||
allow(subscription).to receive(:[]).with('quantity').and_return('10')
|
||||
allow(subscription).to receive(:customer).and_return('cus_123')
|
||||
describe 'subscription deletion handling' do
|
||||
it 'calls CreateStripeCustomerService on subscription deletion' do
|
||||
allow(event).to receive(:type).and_return('customer.subscription.deleted')
|
||||
|
||||
# Create a double for the service
|
||||
customer_service = double
|
||||
allow(Enterprise::Billing::CreateStripeCustomerService).to receive(:new)
|
||||
.with(account: account).and_return(customer_service)
|
||||
allow(customer_service).to receive(:perform)
|
||||
|
||||
stripe_event_service.new.perform(event: event)
|
||||
|
||||
# Verify the service was called
|
||||
expect(Enterprise::Billing::CreateStripeCustomerService).to have_received(:new)
|
||||
.with(account: account)
|
||||
expect(customer_service).to have_received(:perform)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'plan-specific feature management' do
|
||||
context 'with default plan (Hacker)' do
|
||||
it 'disables all premium features' do
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_hacker', 'name' => 'Hacker' })
|
||||
|
||||
# Enable features first
|
||||
described_class::STARTUP_PLAN_FEATURES.each do |feature|
|
||||
account.enable_features(feature)
|
||||
end
|
||||
account.enable_features(*described_class::BUSINESS_PLAN_FEATURES)
|
||||
account.enable_features(*described_class::ENTERPRISE_PLAN_FEATURES)
|
||||
account.save!
|
||||
|
||||
account.reload
|
||||
expect(account).to be_feature_enabled(described_class::STARTUP_PLAN_FEATURES.first)
|
||||
|
||||
stripe_event_service.new.perform(event: event)
|
||||
|
||||
account.reload
|
||||
|
||||
all_features = described_class::STARTUP_PLAN_FEATURES +
|
||||
described_class::BUSINESS_PLAN_FEATURES +
|
||||
described_class::ENTERPRISE_PLAN_FEATURES
|
||||
|
||||
all_features.each do |feature|
|
||||
expect(account).not_to be_feature_enabled(feature)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'enable features on customer.subscription.updated' do
|
||||
allow(event).to receive(:type).and_return('customer.subscription.updated')
|
||||
allow(subscription).to receive(:customer).and_return('cus_123')
|
||||
stripe_event_service.new.perform(event: event)
|
||||
expect(account.reload.custom_attributes).to eq({
|
||||
'captain_responses_usage' => 0,
|
||||
'stripe_customer_id' => 'cus_123',
|
||||
'stripe_price_id' => 'test',
|
||||
'stripe_product_id' => 'plan_id_2',
|
||||
'plan_name' => 'Startups',
|
||||
'subscribed_quantity' => '10',
|
||||
'subscription_ends_on' => Time.zone.at(1_686_567_520).as_json,
|
||||
'subscription_status' => 'active'
|
||||
})
|
||||
expect(account).to be_feature_enabled('channel_email')
|
||||
expect(account).to be_feature_enabled('help_center')
|
||||
context 'with Startups plan' do
|
||||
it 'enables common features but not premium features' do
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' })
|
||||
|
||||
stripe_event_service.new.perform(event: event)
|
||||
|
||||
# Verify basic (Startups) features are enabled
|
||||
account.reload
|
||||
described_class::STARTUP_PLAN_FEATURES.each do |feature|
|
||||
expect(account).to be_feature_enabled(feature)
|
||||
end
|
||||
|
||||
# But business and enterprise features should be disabled
|
||||
described_class::BUSINESS_PLAN_FEATURES.each do |feature|
|
||||
expect(account).not_to be_feature_enabled(feature)
|
||||
end
|
||||
|
||||
described_class::ENTERPRISE_PLAN_FEATURES.each do |feature|
|
||||
expect(account).not_to be_feature_enabled(feature)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Business plan' do
|
||||
it 'enables business-specific features' do
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_business', 'name' => 'Business' })
|
||||
|
||||
stripe_event_service.new.perform(event: event)
|
||||
|
||||
account.reload
|
||||
described_class::STARTUP_PLAN_FEATURES.each do |feature|
|
||||
expect(account).to be_feature_enabled(feature)
|
||||
end
|
||||
|
||||
described_class::BUSINESS_PLAN_FEATURES.each do |feature|
|
||||
expect(account).to be_feature_enabled(feature)
|
||||
end
|
||||
|
||||
described_class::ENTERPRISE_PLAN_FEATURES.each do |feature|
|
||||
expect(account).not_to be_feature_enabled(feature)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Enterprise plan' do
|
||||
it 'enables all business and enterprise features' do
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_enterprise', 'name' => 'Enterprise' })
|
||||
|
||||
stripe_event_service.new.perform(event: event)
|
||||
|
||||
account.reload
|
||||
described_class::STARTUP_PLAN_FEATURES.each do |feature|
|
||||
expect(account).to be_feature_enabled(feature)
|
||||
end
|
||||
|
||||
described_class::BUSINESS_PLAN_FEATURES.each do |feature|
|
||||
expect(account).to be_feature_enabled(feature)
|
||||
end
|
||||
|
||||
described_class::ENTERPRISE_PLAN_FEATURES.each do |feature|
|
||||
expect(account).to be_feature_enabled(feature)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'manually managed features' do
|
||||
let(:service) { stripe_event_service.new }
|
||||
let(:internal_attrs_service) { instance_double(Internal::Accounts::InternalAttributesService) }
|
||||
|
||||
before do
|
||||
# Mock the internal attributes service
|
||||
allow(Internal::Accounts::InternalAttributesService).to receive(:new).with(account).and_return(internal_attrs_service)
|
||||
end
|
||||
|
||||
context 'when downgrading with manually managed features' do
|
||||
it 'preserves manually managed features even when downgrading plans' do
|
||||
# Setup: account has Enterprise plan with manually managed features
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_enterprise', 'name' => 'Enterprise' })
|
||||
|
||||
# Mock manually managed features
|
||||
allow(internal_attrs_service).to receive(:manually_managed_features).and_return(%w[audit_logs custom_roles])
|
||||
|
||||
# First run to apply enterprise plan
|
||||
service.perform(event: event)
|
||||
account.reload
|
||||
|
||||
# Verify features are enabled
|
||||
expect(account).to be_feature_enabled('audit_logs')
|
||||
expect(account).to be_feature_enabled('custom_roles')
|
||||
|
||||
# Now downgrade to Hacker plan (which normally wouldn't have these features)
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_hacker', 'name' => 'Hacker' })
|
||||
|
||||
service.perform(event: event)
|
||||
account.reload
|
||||
|
||||
# Manually managed features should still be enabled despite plan downgrade
|
||||
expect(account).to be_feature_enabled('audit_logs')
|
||||
expect(account).to be_feature_enabled('custom_roles')
|
||||
|
||||
# But other premium features should be disabled
|
||||
expect(account).not_to be_feature_enabled('channel_instagram')
|
||||
expect(account).not_to be_feature_enabled('help_center')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'downgrade handling' do
|
||||
let(:service) { stripe_event_service.new }
|
||||
|
||||
before do
|
||||
# Setup internal attributes service mock to return no manually managed features
|
||||
internal_attrs_service = instance_double(Internal::Accounts::InternalAttributesService)
|
||||
allow(Internal::Accounts::InternalAttributesService).to receive(:new).with(account).and_return(internal_attrs_service)
|
||||
allow(internal_attrs_service).to receive(:manually_managed_features).and_return([])
|
||||
end
|
||||
|
||||
context 'when downgrading from Enterprise to Business plan' do
|
||||
before do
|
||||
# Start with Enterprise plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_enterprise', 'name' => 'Enterprise' })
|
||||
service.perform(event: event)
|
||||
account.reload
|
||||
end
|
||||
|
||||
it 'retains business features but disables enterprise features' do
|
||||
# Verify enterprise features were enabled
|
||||
expect(account).to be_feature_enabled('audit_logs')
|
||||
|
||||
# Downgrade to Business plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_business', 'name' => 'Business' })
|
||||
service.perform(event: event)
|
||||
|
||||
account.reload
|
||||
expect(account).to be_feature_enabled('sla')
|
||||
expect(account).to be_feature_enabled('custom_roles')
|
||||
expect(account).not_to be_feature_enabled('audit_logs')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when downgrading from Business to Startups plan' do
|
||||
before do
|
||||
# Start with Business plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_business', 'name' => 'Business' })
|
||||
service.perform(event: event)
|
||||
account.reload
|
||||
end
|
||||
|
||||
it 'retains startup features but disables business features' do
|
||||
# Verify business features were enabled
|
||||
expect(account).to be_feature_enabled('sla')
|
||||
|
||||
# Downgrade to Startups plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' })
|
||||
service.perform(event: event)
|
||||
|
||||
account.reload
|
||||
# Spot check one startup feature
|
||||
expect(account).to be_feature_enabled('channel_instagram')
|
||||
expect(account).not_to be_feature_enabled('sla')
|
||||
expect(account).not_to be_feature_enabled('custom_roles')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when downgrading from Startups to Hacker plan' do
|
||||
before do
|
||||
# Start with Startups plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' })
|
||||
service.perform(event: event)
|
||||
account.reload
|
||||
end
|
||||
|
||||
it 'disables all premium features' do
|
||||
# Verify startup features were enabled
|
||||
expect(account).to be_feature_enabled('channel_instagram')
|
||||
|
||||
# Downgrade to Hacker (default) plan
|
||||
allow(subscription).to receive(:[]).with('plan')
|
||||
.and_return({ 'id' => 'test', 'product' => 'plan_id_hacker', 'name' => 'Hacker' })
|
||||
service.perform(event: event)
|
||||
|
||||
account.reload
|
||||
# Spot check that premium features are disabled
|
||||
expect(account).not_to be_feature_enabled('channel_instagram')
|
||||
expect(account).not_to be_feature_enabled('help_center')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Internal::Accounts::InternalAttributesService do
|
||||
let!(:account) { create(:account, internal_attributes: { 'test_key' => 'test_value' }) }
|
||||
let(:service) { described_class.new(account) }
|
||||
let(:business_features) { Enterprise::Billing::HandleStripeEventService::BUSINESS_PLAN_FEATURES }
|
||||
let(:enterprise_features) { Enterprise::Billing::HandleStripeEventService::ENTERPRISE_PLAN_FEATURES }
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets the account' do
|
||||
expect(service.account).to eq(account)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get' do
|
||||
it 'returns the value for a valid key' do
|
||||
# Manually set the value first since the key needs to be in VALID_KEYS
|
||||
allow(service).to receive(:validate_key!).and_return(true)
|
||||
account.internal_attributes['manually_managed_features'] = ['test']
|
||||
|
||||
expect(service.get('manually_managed_features')).to eq(['test'])
|
||||
end
|
||||
|
||||
it 'raises an error for an invalid key' do
|
||||
expect { service.get('invalid_key') }.to raise_error(ArgumentError, 'Invalid internal attribute key: invalid_key')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set' do
|
||||
it 'sets the value for a valid key' do
|
||||
# Stub the validation to allow our test key
|
||||
allow(service).to receive(:validate_key!).and_return(true)
|
||||
|
||||
service.set('manually_managed_features', %w[feature1 feature2])
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq(%w[feature1 feature2])
|
||||
end
|
||||
|
||||
it 'raises an error for an invalid key' do
|
||||
expect { service.set('invalid_key', 'value') }.to raise_error(ArgumentError, 'Invalid internal attribute key: invalid_key')
|
||||
end
|
||||
|
||||
it 'creates internal_attributes hash if it is empty' do
|
||||
account.update(internal_attributes: {})
|
||||
|
||||
# Stub the validation to allow our test key
|
||||
allow(service).to receive(:validate_key!).and_return(true)
|
||||
|
||||
service.set('manually_managed_features', ['feature1'])
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq(['feature1'])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#manually_managed_features' do
|
||||
it 'returns an empty array when no features are set' do
|
||||
expect(service.manually_managed_features).to eq([])
|
||||
end
|
||||
|
||||
it 'returns the features when they are set' do
|
||||
account.update(internal_attributes: { 'manually_managed_features' => %w[feature1 feature2] })
|
||||
|
||||
expect(service.manually_managed_features).to eq(%w[feature1 feature2])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#manually_managed_features=' do
|
||||
# Use a real SLA feature which is in the BUSINESS_PLAN_FEATURES
|
||||
let(:valid_feature) { 'sla' }
|
||||
|
||||
before do
|
||||
# Make sure the feature is allowed through validation
|
||||
allow(service).to receive(:valid_feature_list).and_return([valid_feature, 'custom_roles'])
|
||||
end
|
||||
|
||||
it 'saves features as an array' do
|
||||
service.manually_managed_features = valid_feature
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([valid_feature])
|
||||
end
|
||||
|
||||
it 'handles nil input' do
|
||||
service.manually_managed_features = nil
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([])
|
||||
end
|
||||
|
||||
it 'handles array input' do
|
||||
service.manually_managed_features = [valid_feature, 'custom_roles']
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([valid_feature, 'custom_roles'])
|
||||
end
|
||||
|
||||
it 'filters out invalid features' do
|
||||
service.manually_managed_features = [valid_feature, 'invalid_feature']
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([valid_feature])
|
||||
end
|
||||
|
||||
it 'removes duplicates' do
|
||||
service.manually_managed_features = [valid_feature, valid_feature]
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([valid_feature])
|
||||
end
|
||||
|
||||
it 'removes empty strings' do
|
||||
service.manually_managed_features = [valid_feature, '', ' ']
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([valid_feature])
|
||||
end
|
||||
|
||||
it 'trims whitespace' do
|
||||
service.manually_managed_features = [" #{valid_feature} "]
|
||||
account.reload
|
||||
|
||||
expect(account.internal_attributes['manually_managed_features']).to eq([valid_feature])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid_feature_list' do
|
||||
it 'returns a combination of business and enterprise features' do
|
||||
expect(service.valid_feature_list).to include(*business_features)
|
||||
expect(service.valid_feature_list).to include(*enterprise_features)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user