Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ gem 'appraisal'

# Dependencies for dummy application
gem 'sqlite3'
gem 'jsonapi-resources', github: 'cerebris/jsonapi-resources'
gem 'jsonapi-resources', '~> 0.8.0'
gem 'pundit'

gemspec
6 changes: 4 additions & 2 deletions lib/pundit/resource_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module ResourceController
error = Pundit::NotAuthorizedError
unless config.exception_class_whitelist.include? error
config.exception_class_whitelist << error
config.use_relationship_reflection = true
end
end

Expand All @@ -28,11 +29,12 @@ def enforce_policy_use

def reject_forbidden_request(error)
type = error.record.class.name.underscore.humanize(capitalize: false)
human_action = params[:action].humanize(capitalize: false)
error = JSONAPI::Error.new(
code: JSONAPI::FORBIDDEN,
status: :forbidden,
title: "#{params[:action].capitalize} Forbidden",
detail: "You don't have permission to #{params[:action]} this #{type}.",
title: "#{human_action.titleize} Forbidden",
detail: "You don't have permission to #{human_action} this #{type}.",
)

render json: { errors: [error] }, status: 403
Expand Down
6 changes: 6 additions & 0 deletions spec/controllers/application_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "rails_helper"

RSpec.describe ApplicationController, type: :controller do
it { is_expected.to be_a JSONAPI::ResourceController }
it { is_expected.to be_a Pundit::ResourceController }
end
44 changes: 44 additions & 0 deletions spec/controllers/create_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require "rails_helper"

RSpec.describe UsersController, type: :controller do
describe "#create" do
def do_request
post :create, params_hash(data: { type: :users })
end

context "but Pundit says no" do
before do
expect_any_instance_of(UserPolicy).
to receive(:create?).and_return(false)
end

it "does not create a user" do
expect { do_request }.not_to change { User.count }
end

it "responds with 403 Forbidden" do
do_request
expect(response).to have_http_status 403
expect(body.dig(:errors, 0, :title)).to eq "Create Forbidden"
expect(body.dig(:errors, 0, :detail)).to eq <<-DESC.strip
You don't have permission to create this user.
DESC
end
end

context "and Pundit says yes" do
before do
allow_any_instance_of(UserPolicy).to receive(:create?).and_return(true)
end

it "creates a user" do
expect { do_request }.to change { User.count }.by 1
end

it "responds with 201 Created" do
do_request
expect(response).to have_http_status 201
end
end
end
end
64 changes: 64 additions & 0 deletions spec/controllers/destroy_relationship_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require "rails_helper"

RSpec.describe PostsController, type: :controller do
describe "#destroy_relationship" do
let(:params) {{
relationship: "user",
post_id: post_id,
data: {
type: "users",
id: user_id.to_s,
},
}}

def do_request
delete :destroy_relationship, params_hash(params)
end

context "when the post does not exist" do
let(:user_id) { User.create!.id }
let(:post_id) { next_id Post }
before { do_request }

it "responds with 404 Not Found" do
expect(response).to have_http_status 404
end
end

context "when the post exists" do
let!(:post) { Post.create! }
let(:post_id) { post.id }

let!(:user) { User.create! }
let(:user_id) { user.id }

context "but Pundit does not allow updating the post" do
before do
allow_any_instance_of(PostPolicy).
to receive(:update?).and_return(false)
do_request
end

it "responds with 404 Forbidden" do
expect(response).to have_http_status 403
end
end

context "and Pundit allows updating the post" do
before do
allow_any_instance_of(PostPolicy).
to receive(:update?).and_return(true)
do_request
end

it "responds with 204 No Content" do
expect(response).to have_http_status 204
end

it "diassociates post" do
expect(post.reload.user).to be_nil
end
end
end
end
end
91 changes: 91 additions & 0 deletions spec/controllers/destroy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require "rails_helper"

RSpec.describe UsersController, type: :controller do
describe "#destroy" do
def do_request
delete :destroy, params_hash(id: id)
end

context "when the user does not exist" do
let(:id) { next_id User }

before { do_request }

it "responds with 404 Not Found" do
expect(response).to have_http_status 404
end
end

context "when the user exists" do
let!(:user) { User.create! }
let(:id) { user.id }

context "but Pundit says no" do
before do
allow_any_instance_of(UserPolicy).
to receive(:destroy?).and_return(false)
end

context "when the user is not included in the scope" do
before do
allow_any_instance_of(UserPolicy::Scope).
to receive(:resolve).and_return(User.none)
do_request
end

# Even though the resource exists, it is still correct to respond with
# 404 Not Found because the client shouldn't be able to determine
# whether a given resource exists.
#
# From RFC 2616:
#
# > If the server does not wish to make this information available to
# > the client, the status code 404 (Not Found) can be used instead.
it "responds with 404 Not Found" do
expect(response).to have_http_status 404
end

it "contains an error in the JSON response" do
expect(body[:errors].count).to eq 1
end
end

context "when the user is included in the scope" do
before do
allow_any_instance_of(UserPolicy::Scope).
to receive(:resolve).and_return(User.all)
do_request
end

it "responds with 403 Forbidden" do
expect(response).to have_http_status 403
end

it "contains an error in the JSON response" do
expect(body[:errors].count).to eq 1
expect(body.dig(:errors, 0, :title)).to eq "Destroy Forbidden"
expect(body.dig(:errors, 0, :detail)).to eq <<-DESC.strip
You don't have permission to destroy this user.
DESC
end
end
end

context "and Pundit says yes" do
before do
allow_any_instance_of(UserPolicy).
to receive(:destroy?).and_return(true)
end

it "destroys the user" do
expect { do_request }.to change { User.count }.by(-1)
end

it "responds with 204 No Content" do
do_request
expect(response).to have_http_status 204
end
end
end
end
end
79 changes: 79 additions & 0 deletions spec/controllers/get_related_resource_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require "rails_helper"

RSpec.describe UsersController, type: :controller do
describe "#get_related_resource" do
let(:params) {{
relationship: "user",
source: "posts",
post_id: post_id,
}}

def do_request
get :get_related_resource, params_hash(params)
end


context "when the post does not exist" do
let(:post_id) { next_id Post }
before { do_request }

it "responds with 404 Not Found" do
expect(response).to have_http_status 404
end
end

context "when the post exists" do
let!(:post) { Post.create! }
let(:post_id) { post.id }

context "but the post has no user" do
before { do_request }

it "responds with 200 OK" do
expect(response).to have_http_status 200
end

it "does not have user information" do
expect(body).to eq(data: nil)
end
end

context "and the post has a user" do
let!(:user) { post.create_user! }
before { post.save }

context "and Pundit allows the user to be viewed" do
before do
expect_any_instance_of(UserPolicy::Scope).
to receive(:resolve).and_return(User.all)
do_request
end

it "responds with 200 OK" do
expect(response).to have_http_status 200
end

it "responds with the correct user" do
expect(body[:data][:id]).to eq user.id.to_s
end
end

context "but Pundit does not allow the user to be viewed" do
before do
expect_any_instance_of(UserPolicy::Scope).
to receive(:resolve).and_return(User.none)
do_request
end

it "responds with 200 OK" do
expect(response).to have_http_status 200
end

it "does not have user information" do
expect(body).to eq(data: nil)
end
end
end
end
end
end
54 changes: 54 additions & 0 deletions spec/controllers/get_related_resources_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require "rails_helper"

RSpec.describe PostsController, type: :controller do
describe "#get_related_resources" do
let(:params) {{
source: "users",
relationship: "posts",
user_id: user_id,
}}

def do_request
get :get_related_resources, params_hash(params)
end

describe "when the user does not exist" do
let(:user_id) { next_id User }

before { do_request }

it "responds with 404 Not Found" do
expect(response).to have_http_status 404
end
end

describe "when the user exists" do
let(:user) { User.create! }
let(:user_id) { user.id }
let(:posts) { 4.times.map { Post.create! } }

before do
posts.first(2).map { |p| p.update!(user: user) }

expect_any_instance_of(UserPolicy::Scope).
to receive(:resolve).and_return(User.all)

# Make the scope return one post that belongs to the user
# and one that does not, so it can be tested that only the ones that
# belong to the user are eventually returned.
expect_any_instance_of(PostPolicy::Scope).to receive(:resolve).
and_return(Post.where(id: posts.values_at(1, 3).map(&:id)))

do_request
end

it "responds with 200 OK" do
expect(response).to have_http_status 200
end

it "uses the pundit scope and returns only those belonging to the user" do
expect(body[:data].map { |l| l[:id] }).to eq [posts[1].id.to_s]
end
end
end
end
Loading