Есть ли способ смоделировать политику авторизации Pundit в спецификации запроса? - PullRequest
2 голосов
/ 15 января 2020

Я использую Pundit для авторизации в своем приложении Rails, и я тестирую свои запросы. Я уже успешно протестировал политику, но теперь я хочу убедиться, что мой запрос использует эту политику. Я хочу сделать это стандартным способом c, который я могу использовать в ЛЮБОМ запросе spe c (независимо от контроллера, действия и политики). Честно говоря, в этот момент я был бы согласен со словами «ожидать, что любая политика получит разрешение», например, c для всех запросов.

Для действия индекса (которое использует область действия политики) это было легко:

В описании запроса я говорю

RSpec.describe 'GET /companies', type: :request do
  include_context 'invokes policy scope'
end

И затем я определил общий контекст как :

RSpec.shared_context 'invokes policy scope', shared_context: :metadata do
  before do
    expect(Pundit).to receive(:policy_scope!) do |user_context, relation|
      expect(user_context.user).to eq(user)
      expect(user_context.current_group).to eq(group)

      relation
    end
  end
end

Но как мне сделать то же самое для метода авторизации. Я не знаю, какая конкретная политика будет той, которая ее получит, а также конкретная политика, которая вызовет authorize.

Я не понимаю, как pundit не предоставляет настраиваемое сопоставление для проверки того, что определенный запрос фактически вызывает данную политику, или для "имитации" авторизованного / неавторизованного сценария ios, поэтому я проверяю, что мой запрос возвращается правильный код статуса.

Есть идеи?

Ответы [ 3 ]

1 голос
/ 18 января 2020

Итак, у меня есть несколько комментариев ...

Ожидания (expect операторы) должны быть внутри примера (it) блока, а не внутри блока before. В блоке before можно использовать операторы разрешений (например, allow(ClassX).to receive(:method) { object }), модификации данных, которые нельзя выполнить в объявлении тестовых переменных, или триггеры запросов. См. http://www.betterspecs.org/ для некоторых примеров. TL; DR, этот общий контекст не является подходящим способом для проверки.

Способ проверки того, что policy_scope вызывается с указанными параметрами c:

# You can put something generic like this in a shared context and then
# define 'params' and 'scoped_result' as let vars in the specs that include
# the shared context 
let(:request)       { get '/companies' }
let(:params)        { user_context or whatever }
let(:scoped_result) { relation }
# By using abstract variable names here, we make this reusable

it 'calls policy scope' do
  expect(Pundit).to receive(:policy_scope!).with(params)
  request
end

it 'scopes result' do 
  expect(Pundit.policy_scope!(params)).to eq(scoped_result) 
end

Чтобы его смутить и заглушки его ответов, вы должны сделать:

before do
  # This ensures Pundit.policy_scope!(context) always returns scoped_result
  allow(Pundit).to receive(:policy_scope!).with(context) { scoped_result }
end

... но это очень плохие / хрупкие тесты , особенно если это относится к запросу spe c. Ваши политики Pundit уже должны быть протестированы в файлах политики spe c (см. https://github.com/varvet/pundit#rspec), поэтому то, что вы действительно хотите сделать, - это проверить, что ваша конечная точка возвращает правильный вывод (в области видимости) ответ) с учетом определенного ввода (аутентифицированный управляемый объект политики) . Это плохая идея, пытаться переопределить функциональность Pundit, высмеивая ответ, потому что спецификации вашей конечной точки будут продолжать проходить, если вы внесете критические изменения в код своей политики. Здесь вы хотите настроить тестовые переменные в соответствии с обстоятельствами, которые могут привести к успешному запросу, но убедитесь, что все генерируется c, чтобы его можно было использовать повторно. Для спецификации запроса вы можете сделать что-то вроде:

# Shared context stuff
let(:json)    { JSON.parse(response.body) }
let(:headers) { ...define the headers to use across requests...}

before { request }

shared_examples_for 'success' do
  it { expect(response).to have_http_status(:success) }
  it { expect(json).to eq(expected) } # or something
end


# User spec that includes shared context
include_context 'above stuff'

let(:request) { get '/companies', params: params, headers: headers }
let(:params)  { { user_id: user.id } } # or something
let!(:admin_thing) { 
  ...something that should be excluded by the pundit policy used by endpoint... 
}

context 'restricted' do
  let!(:user)    { create :user, :restricted }
  let(:expected) { ...stuff scoped to restricted user... }

  it_behaves_like 'success'
end

context 'manager' do
  let!(:user)    { create :user, :manager }
  let(:expected) { ...stuff scoped to manager user... }

  it_behaves_like 'success'
end

context 'superuser' do
  let!(:user)   { create :user, :superuser }
  let(expected) { ...unscoped stuff visible to super user... }

  it_behaves_like 'success'
end

Обратите внимание, что на более высоком уровне (общий контекст) имя и функция являются обобщенными c. На нижнем уровне (spe c объявляет разрешенных пользователей) spe c преобразует абстрактные имена в значения, указывающие c на тестируемую конечную точку. Спецификация c также создает дополнительный объект, который не должен возвращать областью действия политики (по сути, проверяя область видимости, подтверждая, что этот объект исключен из результата). Надеюсь, это поможет.

1 голос
/ 12 апреля 2020

когда вы включаете модуль Pundit в свои контроллеры, тогда методы policy_scope и autherize становятся доступными в вашем контроллере как методы publi c.

, поэтому при отправке get или post запрос к рельсам вашего контроллера за кулисами создает экземпляр контроллера ControllerClass.new, поэтому вам нужно смоделировать метод authorize для экземпляра объекта контроллера.

, чтобы смоделировать метод по этому объекту вы должны знать или иметь этот объект в своих тестах, что невозможно. но мы надеемся, что вы можете заранее смоделировать метод publi c на любом экземпляре класса.

, поэтому, чтобы смоделировать метод authorize, вы напишите:

expect_any_instance_of(described_class).to receive(:authorize).with(any_params_you_want).and_return(nil)

expect_any_instance_of метод, предоставляемый rspe c для насмешки над любым экземпляром объекта определенного класса. нажмите здесь, чтобы узнать больше

, поэтому больше не нужно ссылаться на класс Pundit в своих тестах. на самом деле это создаст зависимость в наших тестах от имени класса гема, которое вам не нужно, поскольку вы можете протестировать оба метода, как описано выше.

1 голос
/ 16 января 2020

Во-первых, вам нужно проверить свою политику pundit в одиночку. Во-вторых, чтобы пройти политику pundit, вы можете заглушки объекта, который удовлетворяет pundit policy

...