Comparing Pundit RSpec test Approaches
Two approaches, subtle differences. Which one is better?
I am an old-time CanCanCan user and recently started to consider switching to Pundit.
I am also a big fan of RSpec. Given how important Authorization (AuthZ) is, I want to test it properly.
I found two approaches to test Pundit policies with RSpec. Both approaches are described in the Pundit README and I want to compare them in this post.
pundit/rspec
approach is the native approach for Pundit.
It introduces a simple DSL consisting of a permit
matcher permissions
to group them.
pundit-matchers
approach is a third-party approach.
It has a more extensive DSL with a large list of matchers.
You can see the key differences when looking at the examples given in their respective READMEs. (see below)
You will see that the pundit/rspec
approach structures the tests around the actions, while pundit-matchers
structures the tests around the user segments or other contexts.
pundit/rspec
approachHere, everything is centered around the permissions
matcher.
One tests the actions of the policy one-by-one.
describe PostPolicy do
subject { described_class }
permissions :update?, :edit? do
it "denies access if post is published" do
expect(subject).not_to permit(User.new(admin: false), Post.new(published: true))
end
it "grants access if post is published and user is an admin" do
expect(subject).to permit(User.new(admin: true), Post.new(published: true))
end
it "grants access if post is unpublished" do
expect(subject).to permit(User.new(admin: false), Post.new(published: false))
end
end
end
pundit-matchers
approachHere, the permit_*
and forbid_*
matchers are used to test all actions at once.
The code is structured by the user roles.
One defines a context for each user or what else is driving the context of permissions and then all relevant actions are tested at once.
require 'rails_helper'
RSpec.describe ArticlePolicy do
subject { described_class.new(user, article) }
let(:article) { Article.new }
context 'with visitors' do
let(:user) { nil }
it { is_expected.to permit_only_actions(%i[index show]) }
end
context 'with administrators' do
let(:user) { User.new(administrator: true) }
it { is_expected.to permit_all_actions }
end
end
This approach reminds me very well to how I tested my CanCanCan abilities. I had a spec file for each class for which I defined abilities and within that file, I had a context for each user role and other context drivers.
For me, the examples of the READMEs are too small to understand the implications of these differences. So I migrated my CanCanCan tests to Pundit and tried both approaches.
My application Librario is a library management system primarily targeting small and medium-sized companies.
It has the models Accounts, Users, and Roles.
An Account has many User and each User may have roles like admin
or librarian
- or no special role at all.
The policy used for this comparison was AccountPolicy
, which relates to the Account
model.
pundit/rspec
approach in the real-life example# frozen_string_literal: true
require 'rails_helper'
# rubocop:disable RSpec/MultipleMemoizedHelpers
RSpec.describe AccountPolicy, type: :policy do
subject(:policy) { described_class }
let(:account) { create(:account, :with_legacy_subscription) }
let(:guest_user) { User.new(account:) }
let(:user) { create(:user, account:) }
let(:admin) { create(:user, :admin, account:) }
let(:librarian) { create(:user, :librarian, account:) }
describe AccountPolicy::Scope do
subject(:resolved_scope) { described_class.new(user, Account.all).resolve }
it "restricts the scope to the user's account" do
expect(resolved_scope.to_sql).to eq Account.where(id: user.account_id).to_sql
end
end
permissions :login_with_password?, :login_with_sso? do
it { is_expected.not_to permit(user, account) }
it { is_expected.not_to permit(admin, account) }
it { is_expected.not_to permit(librarian, account) }
end
permissions :login_with_password? do
context 'when user is a guest' do
it 'grants access if SSO feature is disabled' do
stub_plan(id: :ingenieurbuero_klein_2014, limits: { user: { sso: false } }, active: true)
expect(policy).to permit(guest_user, account)
end
context 'when SSO feature is enabled' do
before { stub_plan(id: :ingenieurbuero_klein_2014, limits: { user: { sso: true } }, active: true) }
context 'when sso_disable_username_password preference is true' do
before { account.preferences.sso_disable_username_password = true }
it 'denies access if subdomain present' do
account.subdomain = 'test'
expect(policy).not_to permit(guest_user, account)
end
it 'grants access if subdomain blank' do
account.subdomain = nil
expect(policy).to permit(guest_user, account)
end
end
it 'grants access if sso_disable_username_password preference is false' do
account.preferences.sso_disable_username_password = false
expect(policy).to permit(guest_user, account)
end
end
end
end
permissions :login_with_sso? do
context 'when user is a guest' do
it 'denies access if SSO feature is disabled' do
stub_plan(id: :ingenieurbuero_klein_2014, limits: { user: { sso: false } }, active: true)
expect(policy).not_to permit(guest_user, account)
end
context 'when SSO feature is enabled' do
before { stub_plan(id: :ingenieurbuero_klein_2014, limits: { user: { sso: true } }, active: true) }
it 'denies access if subdomain is blank' do
account.subdomain = nil
expect(policy).not_to permit(guest_user, account)
end
it 'grants access if subdomain is present' do
account.subdomain = 'test'
expect(policy).to permit(guest_user, account)
end
end
end
end
permissions :show? do
it { is_expected.not_to permit(guest_user, account) }
it { is_expected.to permit(user, account) }
it { is_expected.to permit(admin, account) }
it { is_expected.to permit(librarian, account) }
end
permissions :show?, :update? do
context 'when users are not member of the account' do
let(:other_account) { create(:account) }
it { is_expected.not_to permit(guest_user, other_account) }
it { is_expected.not_to permit(user, other_account) }
it { is_expected.not_to permit(admin, other_account) }
it { is_expected.not_to permit(librarian, other_account) }
end
end
permissions :create? do
it { is_expected.not_to permit(user, Account) }
it { is_expected.not_to permit(admin, Account) }
it { is_expected.not_to permit(librarian, Account) }
context 'when Config.bc_disable_account_new is true' do
before { allow(Config).to receive(:bc_disable_account_new).and_return(true) }
it { is_expected.not_to permit(guest_user, Account) }
end
context 'when Config.bc_disable_account_new is false' do
before { allow(Config).to receive(:bc_disable_account_new).and_return(false) }
it { is_expected.to permit(guest_user, Account) }
end
end
permissions :update? do
it { is_expected.not_to permit(guest_user, account) }
it { is_expected.not_to permit(user, account) }
it { is_expected.to permit(admin, account) }
it { is_expected.not_to permit(librarian, account) }
end
permissions :destroy? do
it { is_expected.not_to permit(guest_user, account) }
it { is_expected.not_to permit(user, account) }
it { is_expected.not_to permit(admin, account) }
it { is_expected.not_to permit(librarian, account) }
end
describe '#permitted_attributes_for_create' do
subject(:permitted_attributes) { described_class.new(user, account).permitted_attributes_for_create }
context 'with guests' do
let(:user) { User.new(account:) }
context 'with custom subdomain feature enabled' do
before { stub_plan id: :ingenieurbuero_klein_2014, limits: { account: { custom_subdomain: true } }, active: true }
it { is_expected.to include(:subdomain) }
it { is_expected.to contain_exactly(:name, :subdomain, users_attributes: [], subscription_attributes: []) }
end
context 'with custom subdomain feature disabled' do
before { stub_plan id: :ingenieurbuero_klein_2014, limits: { account: { custom_subdomain: false } }, active: true }
it { is_expected.not_to include(:subdomain) }
it { is_expected.to contain_exactly(:name, users_attributes: [], subscription_attributes: []) }
end
end
context 'with users' do
let(:user) { create(:user, account:) }
it { is_expected.to be_empty }
end
context 'with librarians' do
let(:user) { create(:user, :librarian, account:) }
it { is_expected.to be_empty }
end
context 'with admins' do
let(:user) { create(:user, :admin, account:) }
it { is_expected.to be_empty }
end
end
describe '#permitted_attributes_for_update' do
subject(:permitted_attributes) { described_class.new(user, account).permitted_attributes_for_update }
context 'with guests' do
let(:user) { User.new(account:) }
it { is_expected.to be_empty }
end
context 'with users' do
let(:user) { create(:user, account:) }
it { is_expected.to be_empty }
end
context 'with librarians' do
let(:user) { create(:user, :librarian, account:) }
it { is_expected.to be_empty }
end
context 'with admins' do
let(:user) { create(:user, :admin, account:) }
before { allow(Account::PreferencesPolicy).to receive(:new).and_return(instance_double(Account::PreferencesPolicy, permitted_attributes_for_update: [:my_doubled_attributes])) }
it { is_expected.to contain_exactly(:name, :brand, :remove_brand, preferences: [:my_doubled_attributes]) }
end
end
end
# rubocop:enable RSpec/MultipleMemoizedHelpers
pundit-matchers
approach in the real-life example# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AccountPolicy, type: :policy do
subject(:policy) { described_class.new(user, account) }
let(:account) { create(:account, :with_legacy_subscription) }
let(:resolved_scope) do
described_class::Scope.new(user, Account.all).resolve
end
shared_examples_for 'scope limited to current account' do
it 'includes only current account in resolved scope' do
create_list(:account, 3)
expect(resolved_scope).to contain_exactly(account)
end
end
context 'with guests' do
let(:user) { User.new(account:) }
it { is_expected.to permit_new_and_create_actions }
it { is_expected.to forbid_edit_and_update_actions }
it { is_expected.to forbid_action(:show) }
it { is_expected.to forbid_action(:destroy) }
it { is_expected.to permit_action(:login_with_password) }
it { is_expected.to forbid_action(:login_with_sso) }
context 'when Config.bc_disable_account_new is true' do
before { allow(Config).to receive(:bc_disable_account_new).and_return(true) }
it { is_expected.to forbid_new_and_create_actions }
end
context 'with SSO feature disabled' do
before { stub_plan(id: :ingenieurbuero_klein_2014, limits: { user: { sso: false } }, active: true) }
it { is_expected.to permit_action(:login_with_password) }
it { is_expected.to forbid_action(:login_with_sso) }
end
context 'with SSO feature enabled' do
before { stub_plan(id: :ingenieurbuero_klein_2014, limits: { user: { sso: true } }, active: true) }
context 'when sso_disable_username_password preference is true' do
before { account.preferences.sso_disable_username_password = true }
context 'with subdomain present' do
before { account.subdomain = 'test' }
it { is_expected.to forbid_action(:login_with_password) }
it { is_expected.to permit_action(:login_with_sso) }
end
context 'with subdomain blank' do
before { account.subdomain = nil }
it { is_expected.to permit_action(:login_with_password) }
it { is_expected.to forbid_action(:login_with_sso) }
end
end
context 'when sso_disable_username_password preference is false' do
before { account.preferences.sso_disable_username_password = false }
it { is_expected.to permit_action(:login_with_password) }
it { is_expected.to permit_action(:login_with_sso) }
end
end
it_behaves_like 'scope limited to current account'
end
shared_examples_for 'a logged in user' do
it { is_expected.to forbid_new_and_create_actions }
it { is_expected.to forbid_edit_and_update_actions }
it { is_expected.to permit_action(:show) }
it { is_expected.to forbid_action(:destroy) }
it { is_expected.to forbid_action(:login_with_password) }
it { is_expected.to forbid_action(:login_with_sso) }
context 'when accessing a different account' do
let(:user) { create(:user, account: create(:account)) }
it { is_expected.to forbid_all_actions }
end
it_behaves_like 'scope limited to current account'
end
context 'with users' do
let(:user) { create(:user, account:) }
it_behaves_like 'a logged in user'
end
context 'with librarians' do
let(:user) { create(:user, :librarian, account:) }
it_behaves_like 'a logged in user'
end
context 'with admins' do
let(:user) { create(:user, :admin, account:) }
it { is_expected.to forbid_new_and_create_actions }
it { is_expected.to permit_edit_and_update_actions }
it { is_expected.to permit_action(:show) }
it { is_expected.to forbid_action(:destroy) }
it { is_expected.to forbid_action(:login_with_password) }
it { is_expected.to forbid_action(:login_with_sso) }
context 'when accessing a different account' do
let(:user) { create(:user, account: create(:account)) }
it { is_expected.to forbid_all_actions }
end
it_behaves_like 'scope limited to current account'
end
describe 'permitted attributes for create' do
subject(:permitted_attributes) { policy.permitted_attributes_for_create }
context 'with guests' do
let(:user) { User.new(account:) }
context 'with custom subdomain feature enabled' do
before { stub_plan id: :ingenieurbuero_klein_2014, limits: { account: { custom_subdomain: true } }, active: true }
it { is_expected.to include(:subdomain) }
it { is_expected.to contain_exactly(:name, :subdomain, users_attributes: [], subscription_attributes: []) }
end
context 'with custom subdomain feature disabled' do
before { stub_plan id: :ingenieurbuero_klein_2014, limits: { account: { custom_subdomain: false } }, active: true }
it { is_expected.not_to include(:subdomain) }
it { is_expected.to contain_exactly(:name, users_attributes: [], subscription_attributes: []) }
end
end
context 'with users' do
let(:user) { create(:user, account:) }
it { is_expected.to be_empty }
end
context 'with librarians' do
let(:user) { create(:user, :librarian, account:) }
it { is_expected.to be_empty }
end
context 'with admins' do
let(:user) { create(:user, :admin, account:) }
it { is_expected.to be_empty }
end
end
describe 'permitted attributes for update' do
subject(:permitted_attributes) { policy.permitted_attributes_for_update }
context 'with guests' do
let(:user) { User.new(account:) }
it { is_expected.to be_empty }
end
context 'with users' do
let(:user) { create(:user, account:) }
it { is_expected.to be_empty }
end
context 'with librarians' do
let(:user) { create(:user, :librarian, account:) }
it { is_expected.to be_empty }
end
context 'with admins' do
let(:user) { create(:user, :admin, account:) }
before { allow(Account::PreferencesPolicy).to receive(:new).and_return(instance_double(Account::PreferencesPolicy, permitted_attributes_for_update: [:my_doubled_attributes])) }
it { is_expected.to contain_exactly(:name, :brand, :remove_brand, preferences: [:my_doubled_attributes]) }
end
end
end
I like that one can write tests in a very concise way with the pundit-matchers
approach.
All tests can be quickly written and understood using the rspec one-liner syntax.
For example it { is_expected.to permit_action(:login_with_password) }
or it { is_expected.to permit_new_and_create_actions }
are quite clear in what they intend to test.
Theoretically, the one-liner syntax can be also used with the pundit/rspec
approach.
However, the output of the tests using rspec --format documentation
is not as nice as with the pundit-matchers
approach.
This is due to the fact that the permit
matcher of pundit/rspec
expects a object or record
as an argument.
This looks quite verbose and the important part cannot be seen at a glance.
For example, what user (with a certain role) was permitted to act on a certain Account cannot be distinguished from that output.
Example output of a one-liner with the pundit/rspec
approach:
destroy?
is expected not to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 33, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 33, name: "Strieder, Birkemeyer und Strausa", subdomain: "customer-33", created_at: "20...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
Example output of a one-liner with the pundit-matchers
approach:
with admins
is expected to forbid [:create, :new]
On the other side, with the pundit/rspec
approach, one can see at a glance what aspects of an action have been tested and what not, as all test cases are grouped under a permissions
block.
For pundit-matchers
, this is significantly harder.
One looses quickly the overview of what is tested and what not (yet).
I think this is a problem of the DSL and the way how the tests are structured.
One cannot see at a glance what aspects of an action have been covered and which ones not, as all tests covering a certain action are spread over the whole file.
This is actually contrary to the overall idea of Pundit, where one has a simple class for a Policy and one simple method for each action.
Why should one now leave this simple structure behind, when starting to test the policies?
I don’t see the benefit.
What might look nice with the pundit-matchers
approach is that one can use shared examples extensively, as the differences between users/roles to be tests may be subtle.
pundit/rspec
has less use for shared examples in my opinion, as one could group the shared examples using the permissions
block.
For example the permissions :login_with_password?, :login_with_sso?
-block would run the tests against both actions.
Given that, the “advantage” of pundit-matchers
is not really an advantage, but a sign of how scattered the test scenarios are.
What I dislike of the pundit/rspec
approach I implemented is the line # rubocop:disable RSpec/MultipleMemoizedHelpers
.
This is due to the fact, that I am defining each user/role in a separate let
-block right in the beginning of the file and I am reusing them extensively.
I don’t see an alternative, but to disable that cop for this file.
Given the above, I think the pundit/rspec
approach is better suited for my use case, where test cases can be quite complex.
However, I will try to use the pundit-matchers
approach for simple cases, where the pundit/rspec
approach would be too verbose.
I am happy to learn about your perspective and experience with testing Pundit policies. Please leave a comment on Mastodon or Twitter.
pundit/rspec
approachAccountPolicy
AccountPolicy::Scope
restricts the scope to the user's account
login_with_password? und login_with_sso?
is expected not to permit #<User id: 2, email: "emely.fehrig@bechtelar.test", created_at: "2023-06-19 21:30:41.298959665 +0000"...id: 2, first_name: "Emely", last_name: "Fehrig", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 2, name: "Heinrich, Ritosek und Gunther", subdomain: "customer-2", created_at: "2023-06...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 3, email: "patrice_salzmann@zboncak.example", created_at: "2023-06-19 21:30:41.339075511 +...3, first_name: "Patrice", last_name: "Salzmann", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 3, name: "Götz-Pinnock", subdomain: "customer-3", created_at: "2023-06-19 21:30:41.3161...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 4, email: "woytkowska_semih@conn-conroy.test", created_at: "2023-06-19 21:30:41.518506360 ...4, first_name: "Semih", last_name: "Woytkowska", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 4, name: "Schildhauer-Rach", subdomain: "customer-4", created_at: "2023-06-19 21:30:41....me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
login_with_password?
when user is a guest
grants access if SSO feature is disabled
when SSO feature is enabled
grants access if sso_disable_username_password preference is false
when sso_disable_username_password preference is true
denies access if subdomain present
grants access if subdomain blank
login_with_sso?
when user is a guest
denies access if SSO feature is disabled
when SSO feature is enabled
denies access if subdomain is blank
grants access if subdomain is present
show?
is expected not to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 12, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 12, name: "Hermann-Lippe", subdomain: "customer-12", created_at: "2023-06-19 21:30:41.8...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected to permit #<User id: 5, email: "beh.helene@cronin.example", created_at: "2023-06-19 21:30:41.893439700 +0000", ..._id: 13, first_name: "Helene", last_name: "Beh", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 13, name: "Oppong GmbH & Co. KG", subdomain: "customer-13", created_at: "2023-06-19 21:...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected to permit #<User id: 6, email: "leimbach.joelina@padberg-marquardt.test", created_at: "2023-06-19 21:30:41.9419...4, first_name: "Joelina", last_name: "Leimbach", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 14, name: "Malkus, Jess und Storl", subdomain: "customer-14", created_at: "2023-06-19 2...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected to permit #<User id: 7, email: "bauer_natalia@larkin-bergnaum.test", created_at: "2023-06-19 21:30:42.014043747...: 15, first_name: "Natalia", last_name: "Bauer", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 15, name: "Kette-Ruch", subdomain: "customer-15", created_at: "2023-06-19 21:30:41.9878...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
show? und update?
when users are not member of the account
is expected not to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 16, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 17, name: "Sarvari-Moedl", subdomain: "customer-17", created_at: "2023-06-19 21:30:42.0...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 8, email: "sky.boerner@frami-cummerata.example", created_at: "2023-06-19 21:30:42.12501239..._id: 18, first_name: "Sky", last_name: "Börner", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 19, name: "Boruschewski, Hildenbrand und Leipold", subdomain: "customer-19", created_at...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 9, email: "tamina.tschiers@stoltenberg.example", created_at: "2023-06-19 21:30:42.18251206...20, first_name: "Tamina", last_name: "Tschiers", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 21, name: "Roos-Norris", subdomain: "customer-21", created_at: "2023-06-19 21:30:42.210...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 10, email: "polizzi.ashley@anderson-funk.test", created_at: "2023-06-19 21:30:42.263507822... 22, first_name: "Ashley", last_name: "Polizzi", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 23, name: "Siebel OHG", subdomain: "customer-23", created_at: "2023-06-19 21:30:42.2960...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
create?
is expected not to permit #<User id: 11, email: "angela.dingelstedt@kiehn-rosenbaum.example", created_at: "2023-06-19 21:30:42.... first_name: "Angela", last_name: "Dingelstedt", employee_number: nil, phone_number: nil, uuid: nil> and Account(id: integer, name: string, subdomain: citext, created_at: datetime, updated_at: datetime, can...brand_data: jsonb, preferences: jsonb, stripe_customer_data: jsonb, stripe_subscription_data: jsonb)
is expected not to permit #<User id: 12, email: "sammy_luebke@schmidt.example", created_at: "2023-06-19 21:30:42.395901302 +000...id: 25, first_name: "Sammy", last_name: "Lübke", employee_number: nil, phone_number: nil, uuid: nil> and Account(id: integer, name: string, subdomain: citext, created_at: datetime, updated_at: datetime, can...brand_data: jsonb, preferences: jsonb, stripe_customer_data: jsonb, stripe_subscription_data: jsonb)
is expected not to permit #<User id: 13, email: "carina.boehm@spencer-satterfield.example", created_at: "2023-06-19 21:30:42.45...id: 26, first_name: "Carina", last_name: "Böhm", employee_number: nil, phone_number: nil, uuid: nil> and Account(id: integer, name: string, subdomain: citext, created_at: datetime, updated_at: datetime, can...brand_data: jsonb, preferences: jsonb, stripe_customer_data: jsonb, stripe_subscription_data: jsonb)
when Config.bc_disable_account_new is true
is expected not to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 27, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and Account(id: integer, name: string, subdomain: citext, created_at: datetime, updated_at: datetime, can...brand_data: jsonb, preferences: jsonb, stripe_customer_data: jsonb, stripe_subscription_data: jsonb)
when Config.bc_disable_account_new is false
is expected to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 28, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and Account(id: integer, name: string, subdomain: citext, created_at: datetime, updated_at: datetime, can...brand_data: jsonb, preferences: jsonb, stripe_customer_data: jsonb, stripe_subscription_data: jsonb)
update?
is expected not to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 29, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 29, name: "Sack AG", subdomain: "customer-29", created_at: "2023-06-19 21:30:42.6172760...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 14, email: "zach_karim@stehr-dare.test", created_at: "2023-06-19 21:30:42.681501412 +0000"..._id: 30, first_name: "Karim", last_name: "Zach", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 30, name: "Knoll-Hördt", subdomain: "customer-30", created_at: "2023-06-19 21:30:42.654...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected to permit #<User id: 15, email: "wiese_cheyenne@howe.example", created_at: "2023-06-19 21:30:42.746309119 +0000... 31, first_name: "Cheyenne", last_name: "Wiese", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 31, name: "Ziegler AG", subdomain: "customer-31", created_at: "2023-06-19 21:30:42.7140...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 16, email: "aliyah_frantz@bosco.example", created_at: "2023-06-19 21:30:42.816685330 +0000...: 32, first_name: "Aliyah", last_name: "Frantz", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 32, name: "Mächtig-Kahlert", subdomain: "customer-32", created_at: "2023-06-19 21:30:42...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
destroy?
is expected not to permit #<User id: nil, email: "", created_at: nil, updated_at: nil, locale: nil, account_id: 33, first_name: nil, last_name: nil, employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 33, name: "Strieder, Birkemeyer und Strausa", subdomain: "customer-33", created_at: "20...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 17, email: "moeldner_leann@wisoky.test", created_at: "2023-06-19 21:30:42.907541939 +0000"...: 34, first_name: "Leann", last_name: "Möldner", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 34, name: "Kleiss, Brix und Förster", subdomain: "customer-34", created_at: "2023-06-19...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 18, email: "bolm_ina@muller-lebsack.test", created_at: "2023-06-19 21:30:42.955454805 +000...nt_id: 35, first_name: "Ina", last_name: "Bolm", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 35, name: "Dressler KG", subdomain: "customer-35", created_at: "2023-06-19 21:30:42.928...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
is expected not to permit #<User id: 19, email: "ahlke_elia@conn.test", created_at: "2023-06-19 21:30:43.031144494 +0000", upda..._id: 36, first_name: "Elia", last_name: "Ahlke", employee_number: nil, phone_number: nil, uuid: nil> and #<Account id: 36, name: "Berner, Moguenara und Allgeyer", subdomain: "customer-36", created_at: "2023...me_password: false, welcome_message: nil>, stripe_customer_data: nil, stripe_subscription_data: nil>
#permitted_attributes_for_create
with guests
with custom subdomain feature enabled
is expected to include :subdomain
is expected to contain exactly :name, :subdomain, and {:subscription_attributes=>[], :users_attributes=>[]}
with custom subdomain feature disabled
is expected not to include :subdomain
is expected to contain exactly :name and {:subscription_attributes=>[], :users_attributes=>[]}
with users
is expected to be empty
with librarians
is expected to be empty
with admins
is expected to be empty
#permitted_attributes_for_update
with guests
is expected to be empty
with users
is expected to be empty
with librarians
is expected to be empty
with admins
is expected to contain exactly :name, :brand, :remove_brand, and {:preferences=>[:my_doubled_attributes]}
pundit/rspec
approachAccountPolicy
with guests
is expected to permit [:create, :new]
is expected to forbid [:edit, :update]
is expected to forbid [:show]
is expected to forbid [:destroy]
is expected to permit [:login_with_password]
is expected to forbid [:login_with_sso]
when Config.bc_disable_account_new is true
is expected to forbid [:create, :new]
with SSO feature disabled
is expected to permit [:login_with_password]
is expected to forbid [:login_with_sso]
with SSO feature enabled
when sso_disable_username_password preference is true
with subdomain present
is expected to forbid [:login_with_password]
is expected to permit [:login_with_sso]
with subdomain blank
is expected to permit [:login_with_password]
is expected to forbid [:login_with_sso]
when sso_disable_username_password preference is false
is expected to permit [:login_with_password]
is expected to permit [:login_with_sso]
behaves like scope limited to current account
includes only current account in resolved scope
with users
behaves like a logged in user
is expected to forbid [:create, :new]
is expected to forbid [:edit, :update]
is expected to permit [:show]
is expected to forbid [:destroy]
is expected to forbid [:login_with_password]
is expected to forbid [:login_with_sso]
when accessing a different account
is expected to forbid all actions
behaves like scope limited to current account
includes only current account in resolved scope
with librarians
behaves like a logged in user
is expected to forbid [:create, :new]
is expected to forbid [:edit, :update]
is expected to permit [:show]
is expected to forbid [:destroy]
is expected to forbid [:login_with_password]
is expected to forbid [:login_with_sso]
when accessing a different account
is expected to forbid all actions
behaves like scope limited to current account
includes only current account in resolved scope
with admins
is expected to forbid [:create, :new]
is expected to permit [:edit, :update]
is expected to permit [:show]
is expected to forbid [:destroy]
is expected to forbid [:login_with_password]
is expected to forbid [:login_with_sso]
when accessing a different account
is expected to forbid all actions
behaves like scope limited to current account
includes only current account in resolved scope
permitted attributes for create
with guests
with custom subdomain feature enabled
is expected to include :subdomain
is expected to contain exactly :name, :subdomain, and {:subscription_attributes=>[], :users_attributes=>[]}
with custom subdomain feature disabled
is expected not to include :subdomain
is expected to contain exactly :name and {:subscription_attributes=>[], :users_attributes=>[]}
with users
is expected to be empty
with librarians
is expected to be empty
with admins
is expected to be empty
permitted attributes for update
with guests
is expected to be empty
with users
is expected to be empty
with librarians
is expected to be empty
with admins
is expected to contain exactly :name, :brand, :remove_brand, and {:preferences=>[:my_doubled_attributes]}