summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessandro Desantis <desa.alessandro@gmail.com>2020-10-08 13:49:22 +0200
committerGitHub <noreply@github.com>2020-10-08 13:49:22 +0200
commit4bafb311fe180f3cc0bb38f01f24f2b487e8be8b (patch)
tree83940eec6d1e693cf43e4c3b95a6d2e8233b9cd8
parentfb0777566944c9deef6cbacd7a7eac335838746a (diff)
parent1f6e8d5747f6fa995fb18443d1e264dbd779d124 (diff)
Merge pull request #158 from solidusio-contrib/aldesantis/guest-token
Enable authorization via guest tokens
-rw-r--r--.rubocop.yml4
-rw-r--r--app/controllers/solidus_subscriptions/api/v1/base_controller.rb13
-rw-r--r--app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb25
-rw-r--r--app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb13
-rw-r--r--app/controllers/spree/admin/subscriptions_controller.rb4
-rw-r--r--app/models/solidus_subscriptions/subscription.rb8
-rw-r--r--config/initializers/permission_sets.rb7
-rw-r--r--config/routes.rb2
-rw-r--r--db/migrate/20201007140032_add_guest_token_to_subscriptions.rb6
-rw-r--r--lib/solidus_subscriptions.rb2
-rw-r--r--lib/solidus_subscriptions/ability.rb19
-rw-r--r--lib/solidus_subscriptions/engine.rb6
-rw-r--r--lib/solidus_subscriptions/permission_sets/subscription_management.rb19
-rw-r--r--spec/controllers/solidus_subscriptions/api/v1/line_items_controller_spec.rb124
-rw-r--r--spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb121
-rw-r--r--spec/lib/solidus_subscriptions/ability_spec.rb70
-rw-r--r--spec/lib/solidus_subscriptions/permission_sets/subscription_management_spec.rb95
-rw-r--r--spec/models/solidus_subscriptions/subscription_spec.rb6
-rw-r--r--spec/requests/api/v1/line_items_spec.rb114
-rw-r--r--spec/requests/api/v1/subscriptions_spec.rb155
-rw-r--r--spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb45
-rw-r--r--spec/support/cancancan.rb1
22 files changed, 455 insertions, 404 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 4714d8d..48c6a23 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -2,3 +2,7 @@ inherit_from: .rubocop_todo.yml
require:
- solidus_dev_support/rubocop
+
+RSpec/DescribeClass:
+ Exclude:
+ - spec/requests/**/*
diff --git a/app/controllers/solidus_subscriptions/api/v1/base_controller.rb b/app/controllers/solidus_subscriptions/api/v1/base_controller.rb
new file mode 100644
index 0000000..a1cbb06
--- /dev/null
+++ b/app/controllers/solidus_subscriptions/api/v1/base_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module SolidusSubscriptions
+ module Api
+ module V1
+ class BaseController < ::Spree::Api::BaseController
+ def subscription_guest_token
+ request.headers['X-Spree-Subscription-Token']
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb b/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb
index 1d6817a..b7a2507 100644
--- a/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb
+++ b/app/controllers/solidus_subscriptions/api/v1/line_items_controller.rb
@@ -3,12 +3,14 @@
module SolidusSubscriptions
module Api
module V1
- class LineItemsController < ::Spree::Api::BaseController
- before_action :load_line_item, only: [:update, :destroy]
+ class LineItemsController < BaseController
+ protect_from_forgery unless: -> { request.format.json? }
+
wrap_parameters :subscription_line_item
def update
- authorize! :update, @line_item, @order
+ load_line_item
+
if @line_item.update(line_item_params)
render json: @line_item.to_json
else
@@ -17,26 +19,29 @@ module SolidusSubscriptions
end
def destroy
- authorize! :destroy, @line_item, @order
- return render json: {}, status: :bad_request if @line_item.order.complete?
+ load_line_item
@line_item.destroy!
- @line_item.order.recalculate
+
+ if @line_item.order && !@line_item.order.complete?
+ @line_item.order.recalculate
+ end
render json: @line_item.to_json
end
private
+ def load_line_item
+ @line_item = SolidusSubscriptions::LineItem.find(params[:id])
+ authorize! action_name, @line_item, subscription_guest_token
+ end
+
def line_item_params
params.require(:subscription_line_item).permit(
SolidusSubscriptions::PermittedAttributes.subscription_line_item_attributes - [:subscribable_id]
)
end
-
- def load_line_item
- @line_item = SolidusSubscriptions::LineItem.find(params[:id])
- end
end
end
end
diff --git a/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb b/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb
index 3f0c384..4449a5d 100644
--- a/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb
+++ b/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb
@@ -3,12 +3,12 @@
module SolidusSubscriptions
module Api
module V1
- class SubscriptionsController < ::Spree::Api::BaseController
- before_action :load_subscription, only: [:cancel, :update, :skip]
-
+ class SubscriptionsController < BaseController
protect_from_forgery unless: -> { request.format.json? }
def update
+ load_subscription
+
if @subscription.update(subscription_params)
render json: @subscription.to_json(include: [:line_items, :shipping_address, :billing_address])
else
@@ -17,6 +17,8 @@ module SolidusSubscriptions
end
def skip
+ load_subscription
+
if @subscription.skip
render json: @subscription.to_json
else
@@ -25,6 +27,8 @@ module SolidusSubscriptions
end
def cancel
+ load_subscription
+
if @subscription.cancel
render json: @subscription.to_json
else
@@ -35,7 +39,8 @@ module SolidusSubscriptions
private
def load_subscription
- @subscription = current_api_user.subscriptions.find(params[:id])
+ @subscription = SolidusSubscriptions::Subscription.find(params[:id])
+ authorize! action_name, @subscription, subscription_guest_token
end
def subscription_params
diff --git a/app/controllers/spree/admin/subscriptions_controller.rb b/app/controllers/spree/admin/subscriptions_controller.rb
index cde63a0..6c92ace 100644
--- a/app/controllers/spree/admin/subscriptions_controller.rb
+++ b/app/controllers/spree/admin/subscriptions_controller.rb
@@ -6,9 +6,7 @@ module Spree
skip_before_action :load_resource, only: :index
def index
- @search = SolidusSubscriptions::Subscription.
- accessible_by(current_ability, :index).ransack(params[:q])
-
+ @search = SolidusSubscriptions::Subscription.ransack(params[:q])
@subscriptions = @search.result(distinct: true).
includes(:line_items, :user).
page(params[:page]).
diff --git a/app/models/solidus_subscriptions/subscription.rb b/app/models/solidus_subscriptions/subscription.rb
index 0c41604..d3dd2f5 100644
--- a/app/models/solidus_subscriptions/subscription.rb
+++ b/app/models/solidus_subscriptions/subscription.rb
@@ -31,6 +31,7 @@ module SolidusSubscriptions
accepts_nested_attributes_for :line_items, allow_destroy: true, reject_if: ->(p) { p[:quantity].blank? }
before_validation :set_payment_method
+ before_create :generate_guest_token
after_create :emit_event_for_creation
before_update :update_actionable_date_if_interval_changed
after_update :emit_events_for_update
@@ -275,6 +276,13 @@ module SolidusSubscriptions
end
end
+ def generate_guest_token
+ self.guest_token ||= loop do
+ random_token = SecureRandom.urlsafe_base64(nil, false)
+ break random_token unless self.class.exists?(guest_token: random_token)
+ end
+ end
+
def emit_event_for_creation
::Spree::Event.fire(
'solidus_subscriptions.subscription_created',
diff --git a/config/initializers/permission_sets.rb b/config/initializers/permission_sets.rb
new file mode 100644
index 0000000..b4acf71
--- /dev/null
+++ b/config/initializers/permission_sets.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+Spree.config do |config|
+ config.roles.assign_permissions :default, %w[
+ SolidusSubscriptions::PermissionSets::SubscriptionManagement
+ ]
+end
diff --git a/config/routes.rb b/config/routes.rb
index eb4c8f3..147f747 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
SolidusSubscriptions::Engine.routes.draw do
- namespace :api do
+ namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :line_items, only: [:update, :destroy]
resources :subscriptions, only: [:update] do
diff --git a/db/migrate/20201007140032_add_guest_token_to_subscriptions.rb b/db/migrate/20201007140032_add_guest_token_to_subscriptions.rb
new file mode 100644
index 0000000..8ed2ecb
--- /dev/null
+++ b/db/migrate/20201007140032_add_guest_token_to_subscriptions.rb
@@ -0,0 +1,6 @@
+class AddGuestTokenToSubscriptions < ActiveRecord::Migration[5.2]
+ def change
+ add_column :solidus_subscriptions_subscriptions, :guest_token, :string
+ add_index :solidus_subscriptions_subscriptions, :guest_token, unique: true
+ end
+end
diff --git a/lib/solidus_subscriptions.rb b/lib/solidus_subscriptions.rb
index a6b2edf..21a9b40 100644
--- a/lib/solidus_subscriptions.rb
+++ b/lib/solidus_subscriptions.rb
@@ -7,7 +7,7 @@ require 'deface'
require 'state_machines'
require 'solidus_subscriptions/configuration'
-require 'solidus_subscriptions/ability'
+require 'solidus_subscriptions/permission_sets/subscription_management'
require 'solidus_subscriptions/version'
require 'solidus_subscriptions/engine'
diff --git a/lib/solidus_subscriptions/ability.rb b/lib/solidus_subscriptions/ability.rb
deleted file mode 100644
index 55b6344..0000000
--- a/lib/solidus_subscriptions/ability.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module SolidusSubscriptions
- class Ability
- include CanCan::Ability
-
- def initialize(user)
- if user.has_spree_role?('admin')
- can(:manage, LineItem)
- can(:manage, Subscription)
- else
- can([:index, :show, :create, :update, :destroy, :skip, :cancel], Subscription, user_id: user.id)
- can([:index, :show, :create, :update, :destroy], LineItem) do |line_item, order|
- line_item.order.user == user || line_item.order == order
- end
- end
- end
- end
-end
diff --git a/lib/solidus_subscriptions/engine.rb b/lib/solidus_subscriptions/engine.rb
index ad43db3..c45ecfc 100644
--- a/lib/solidus_subscriptions/engine.rb
+++ b/lib/solidus_subscriptions/engine.rb
@@ -47,12 +47,6 @@ module SolidusSubscriptions
)
end
end
-
- def self.activate
- ::Spree::Ability.register_ability(SolidusSubscriptions::Ability)
- end
-
- config.to_prepare(&method(:activate).to_proc)
end
def self.table_name_prefix
diff --git a/lib/solidus_subscriptions/permission_sets/subscription_management.rb b/lib/solidus_subscriptions/permission_sets/subscription_management.rb
new file mode 100644
index 0000000..c669368
--- /dev/null
+++ b/lib/solidus_subscriptions/permission_sets/subscription_management.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module SolidusSubscriptions
+ module PermissionSets
+ class SubscriptionManagement < ::Spree::PermissionSets::Base
+ def activate!
+ can :manage, Subscription do |subscription, guest_token|
+ (subscription.guest_token.present? && subscription.guest_token == guest_token) ||
+ (subscription.user && subscription.user == user)
+ end
+
+ can :manage, LineItem do |line_item, guest_token|
+ (line_item.subscription&.guest_token.present? && line_item.subscription.guest_token == guest_token) ||
+ (line_item.subscription&.user && line_item.subscription.user == user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/solidus_subscriptions/api/v1/line_items_controller_spec.rb b/spec/controllers/solidus_subscriptions/api/v1/line_items_controller_spec.rb
deleted file mode 100644
index bf8ac5f..0000000
--- a/spec/controllers/solidus_subscriptions/api/v1/line_items_controller_spec.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe SolidusSubscriptions::Api::V1::LineItemsController, type: :controller do
- routes { SolidusSubscriptions::Engine.routes }
-
- let!(:user) { create(:user) }
- let(:line) { create :subscription_line_item, order: order }
-
- before { user.generate_spree_api_key! }
-
- describe "#update" do
- subject { post :update, params: params }
-
- let(:params) do
- {
- id: line.id,
- subscription_line_item: { quantity: 21 },
- token: user.spree_api_key,
- format: :json
- }
- end
-
- context 'guest user' do
- let(:order) { create :order }
-
- let(:params) do
- {
- id: line.id,
- subscription_line_item: { quantity: 21 },
- checkout_id: order.number,
- order_token: order.guest_token,
- order_id: order.number
- }
- end
-
- context "with valid params" do
- let(:json_body) { JSON.parse(subject.body) }
-
- it { is_expected.to be_successful }
-
- it "returns the updated record" do
- expect(json_body["quantity"]).to eq 21
- end
- end
-
- context "with invalid params" do
- let(:params) do
- {
- id: line.id,
- subscription_line_item: { interval_length: -1 },
- checkout_id: order.number,
- order_token: order.guest_token
- }
- end
-
- it { is_expected.to be_unprocessable }
- end
- end
-
- context "when the order belongs to the user" do
- let(:order) { create :order, user: user }
-
- context "with valid params" do
- let(:json_body) { JSON.parse(subject.body) }
-
- it { is_expected.to be_successful }
-
- it "returns the updated record" do
- expect(json_body["quantity"]).to eq 21
- end
- end
-
- context "with invalid params" do
- let(:params) do
- {
- id: line.id,
- subscription_line_item: { interval_length: -1 },
- token: user.spree_api_key,
- format: :json
- }
- end
-
- it { is_expected.to be_unprocessable }
- end
- end
-
- context "when the order belongs to someone else" do
- let(:order) { create :order, user: create(:user) }
-
- it { is_expected.to be_unauthorized }
- end
- end
-
- describe "#destroy" do
- subject { delete :destroy, params: params }
-
- let(:params) {
- {
- id: line.id,
- order_id: order.id,
- token: user.spree_api_key,
- format: :json
- }
- }
-
- context "when the order is not ours" do
- let(:order) { create :order, user: create(:user) }
-
- it { is_expected.to be_unauthorized }
- end
-
- context "when the order is finalised" do
- let(:order) { create :completed_order_with_totals, user: user }
-
- it { is_expected.to be_bad_request }
- end
-
- context "when the order is ours and incomplete" do
- let(:order) { create :order, user: user }
-
- it { is_expected.to be_successful }
- end
- end
-end
diff --git a/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb b/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb
deleted file mode 100644
index 90dd089..0000000
--- a/spec/controllers/solidus_subscriptions/api/v1/subscriptions_controller_spec.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe SolidusSubscriptions::Api::V1::SubscriptionsController, type: :controller do
- routes { SolidusSubscriptions::Engine.routes }
-
- let!(:user) { create :user }
-
- before { user.generate_spree_api_key! }
-
- shared_examples "an authenticated subscription" do
- context "when the subscription belongs to user" do
- let!(:subscription) do
- create(
- :subscription,
- :with_line_item,
- actionable_date: (Date.current + 1.month ),
- user: user
- )
- end
-
- it { is_expected.to be_successful }
- end
-
- context "when the subscription belongs to someone else" do
- let!(:subscription) { create :subscription, user: create(:user) }
-
- it { is_expected.to be_not_found }
- end
-
- context 'when the subscription is canceled' do
- let!(:subscription) { create :subscription, user: user, state: 'canceled' }
-
- it { is_expected.to be_unprocessable }
- end
- end
-
- describe 'PATCH :update' do
- subject { patch :update, params: params }
-
- let(:params) do
- {
- id: subscription.id,
- token: user.spree_api_key,
- subscription: subscription_params
- }
- end
-
- let(:address_country) { create(:country) }
- let(:address_state) { create(:state, country: address_country) }
-
- let(:subscription_params) do
- {
- line_items_attributes: [{
- id: subscription.line_items.first.id,
- quantity: 6
- }],
- shipping_address_attributes: {
- firstname: 'Ash',
- lastname: 'Ketchum',
- address1: '1 Rainbow Road',
- city: 'Palette Town',
- country_id: address_country.id,
- state_id: address_state.id,
- phone: '999-999-999',
- zipcode: '10001'
- },
- billing_address_attributes: {
- firstname: 'Ash',
- lastname: 'Ketchum',
- address1: '1 Rainbow Road',
- city: 'Palette Town',
- country_id: address_country.id,
- state_id: address_state.id,
- phone: '999-999-999',
- zipcode: '10001'
- }
- }
- end
-
- context 'when the subscription belongs to the user' do
- let!(:subscription) { create :subscription, :with_line_item, user: user }
-
- it { is_expected.to be_successful }
-
- context 'when the params are not valid' do
- let(:subscription_params) do
- {
- line_items_attributes: [{
- id: subscription.line_items.first.id,
- quantity: -6
- }]
- }
- end
-
- it { is_expected.to have_http_status(:unprocessable_entity) }
- end
- end
-
- context 'when the subscription belongs to someone else' do
- let!(:subscription) { create :subscription, :with_line_item, user: create(:user) }
-
- it { is_expected.to be_not_found }
- end
- end
-
- describe "POST :skip" do
- subject { post :skip, params: params }
-
- let(:params) { { id: subscription.id, token: user.spree_api_key } }
-
- it_behaves_like "an authenticated subscription"
- end
-
- describe "POST :cancel" do
- subject { post :cancel, params: params }
-
- let(:params) { { id: subscription.id, token: user.spree_api_key } }
-
- it_behaves_like "an authenticated subscription"
- end
-end
diff --git a/spec/lib/solidus_subscriptions/ability_spec.rb b/spec/lib/solidus_subscriptions/ability_spec.rb
deleted file mode 100644
index 07d48ab..0000000
--- a/spec/lib/solidus_subscriptions/ability_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'spec_helper'
-require "cancan/matchers"
-
-RSpec.describe SolidusSubscriptions::Ability do
- subject { described_class.new user }
-
- context 'when the user is a default customer' do
- let(:user) { create :user }
-
- context 'owns the order' do
- let(:order) { create :order }
- let(:line_item) do
- create :subscription_line_item, order: order
- end
-
- it { is_expected.to be_able_to :index, line_item, order }
- it { is_expected.to be_able_to :show, line_item, order }
- it { is_expected.to be_able_to :create, line_item, order }
- it { is_expected.to be_able_to :update, line_item, order }
- it { is_expected.to be_able_to :destroy, line_item, order }
- end
-
- context 'doesnt own the order' do
- let(:order) { create :order }
- let(:another_order) { create :order }
-
- let(:line_item) do
- create :subscription_line_item, order: order
- end
-
- it { is_expected.not_to be_able_to :index, line_item, another_order }
- it { is_expected.not_to be_able_to :show, line_item, another_order }
- it { is_expected.not_to be_able_to :create, line_item, another_order }
- it { is_expected.not_to be_able_to :update, line_item, another_order }
- it { is_expected.not_to be_able_to :destroy, line_item, another_order }
- end
-
- context 'the user owns a subscription' do
- let(:subscription) { create :subscription, user: user }
-
- it { is_expected.to be_able_to :index, subscription }
- it { is_expected.to be_able_to :show, subscription }
- it { is_expected.to be_able_to :create, subscription }
- it { is_expected.to be_able_to :update, subscription }
- it { is_expected.to be_able_to :destroy, subscription }
- it { is_expected.to be_able_to :skip, subscription }
- it { is_expected.to be_able_to :cancel, subscription }
- end
-
- context 'the doesnt own a subscription' do
- let(:another_user) { create :user }
- let(:subscription) { create :subscription, user: another_user }
-
- it { is_expected.not_to be_able_to :index, subscription }
- it { is_expected.not_to be_able_to :show, subscription }
- it { is_expected.not_to be_able_to :create, subscription }
- it { is_expected.not_to be_able_to :update, subscription }
- it { is_expected.not_to be_able_to :destroy, subscription }
- it { is_expected.not_to be_able_to :skip, subscription }
- it { is_expected.not_to be_able_to :cancel, subscription }
- end
- end
-
- context 'the user is an admin' do
- let(:user) { create :admin_user }
-
- it { is_expected.to be_able_to :manage, SolidusSubscriptions::Subscription }
- it { is_expected.to be_able_to :manage, SolidusSubscriptions::LineItem }
- end
-end
diff --git a/spec/lib/solidus_subscriptions/permission_sets/subscription_management_spec.rb b/spec/lib/solidus_subscriptions/permission_sets/subscription_management_spec.rb
new file mode 100644
index 0000000..7079c8a
--- /dev/null
+++ b/spec/lib/solidus_subscriptions/permission_sets/subscription_management_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+RSpec.describe SolidusSubscriptions::PermissionSets::SubscriptionManagement do
+ context 'when the user is authenticated' do
+ it 'is allowed to manage their subscriptions' do
+ user = create(:user)
+ subscription = create(:subscription, user: user)
+
+ ability = Spree::Ability.new(user)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).to be_able_to(:manage, subscription)
+ end
+
+ it "is allowed to manage someone else's subscriptions" do
+ user = create(:user)
+ subscription = create(:subscription)
+
+ ability = Spree::Ability.new(user)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).not_to be_able_to(:manage, subscription)
+ end
+
+ it 'is allowed to manage line items on their subscriptions' do
+ user = create(:user)
+ subscription = create(:subscription, user: user)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ ability = Spree::Ability.new(user)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).to be_able_to(:manage, line_item)
+ end
+
+ it "is not allowed to manage line items on someone else's subscriptions" do
+ user = create(:user)
+ subscription = create(:subscription)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ ability = Spree::Ability.new(user)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).not_to be_able_to(:manage, line_item)
+ end
+ end
+
+ context 'when the user provides a guest token' do
+ it 'is allowed to manage their subscriptions' do
+ subscription = create(:subscription)
+
+ ability = Spree::Ability.new(nil)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).to be_able_to(:manage, subscription, subscription.guest_token)
+ end
+
+ it "is allowed to manage someone else's subscriptions" do
+ subscription = create(:subscription)
+
+ ability = Spree::Ability.new(nil)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).not_to be_able_to(:manage, subscription, 'invalid')
+ end
+
+ it 'is allowed to manage line items on their subscriptions' do
+ subscription = create(:subscription)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ ability = Spree::Ability.new(nil)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).to be_able_to(:manage, line_item, subscription.guest_token)
+ end
+
+ it "is not allowed to manage line items on someone else's subscriptions" do
+ subscription = create(:subscription)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ ability = Spree::Ability.new(nil)
+ permission_set = described_class.new(ability)
+ permission_set.activate!
+
+ expect(ability).not_to be_able_to(:manage, line_item, 'invalid')
+ end
+ end
+end
diff --git a/spec/models/solidus_subscriptions/subscription_spec.rb b/spec/models/solidus_subscriptions/subscription_spec.rb
index 800c713..60a37e6 100644
--- a/spec/models/solidus_subscriptions/subscription_spec.rb
+++ b/spec/models/solidus_subscriptions/subscription_spec.rb
@@ -20,6 +20,12 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
subscription: subscription,
)
end
+
+ it 'generates a guest token' do
+ subscription = create(:subscription)
+
+ expect(subscription.guest_token).to be_present
+ end
end
describe 'updating a subscription' do
diff --git a/spec/requests/api/v1/line_items_spec.rb b/spec/requests/api/v1/line_items_spec.rb
new file mode 100644
index 0000000..c6e34e6
--- /dev/null
+++ b/spec/requests/api/v1/line_items_spec.rb
@@ -0,0 +1,114 @@
+RSpec.describe '/api/v1/line_items' do
+ include SolidusSubscriptions::Engine.routes.url_helpers
+
+ describe 'PATCH /:id' do
+ context 'when the subscription belongs to the user' do
+ context 'with valid params' do
+ it 'responds with 200 OK' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ patch(
+ api_v1_line_item_path(line_item),
+ params: { subscription_line_item: { quantity: 11 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(200)
+ end
+
+ it 'updates the line item' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ patch(
+ api_v1_line_item_path(line_item),
+ params: { subscription_line_item: { quantity: 11 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(line_item.reload.quantity).to eq(11)
+ end
+ end
+
+ context 'with invalid params' do
+ it 'responds with 422 Unprocessable Entity' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ patch(
+ api_v1_line_item_path(line_item),
+ params: { subscription_line_item: { quantity: -1 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(422)
+ end
+ end
+ end
+
+ context 'when the subscription does not belong to the user' do
+ it 'responds with 401 Unauthorized' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ patch(
+ api_v1_line_item_path(line_item),
+ params: { subscription_line_item: { quantity: 11 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /:id' do
+ context 'when the subscription belongs to the user' do
+ it 'responds with 200 OK' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ delete(
+ api_v1_line_item_path(line_item),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(200)
+ end
+
+ it 'deletes the line item' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ delete(
+ api_v1_line_item_path(line_item),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect { line_item.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'when the subscription does not belong to the user' do
+ it 'responds with 401 Unauthorized' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription)
+ line_item = create(:subscription_line_item, subscription: subscription)
+
+ delete(
+ api_v1_line_item_path(line_item),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v1/subscriptions_spec.rb b/spec/requests/api/v1/subscriptions_spec.rb
new file mode 100644
index 0000000..0406ea7
--- /dev/null
+++ b/spec/requests/api/v1/subscriptions_spec.rb
@@ -0,0 +1,155 @@
+RSpec.describe '/api/v1/subscriptions' do
+ include SolidusSubscriptions::Engine.routes.url_helpers
+
+ describe 'PATCH /:id' do
+ context 'when the subscription belongs to the user' do
+ context 'with valid params' do
+ it 'responds with 200 OK' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+
+ patch(
+ api_v1_subscription_path(subscription),
+ params: { subscription: { interval_length: 11 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(200)
+ end
+
+ it 'updates the subscription' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+
+ patch(
+ api_v1_subscription_path(subscription),
+ params: { subscription: { interval_length: 11 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(subscription.reload.interval_length).to eq(11)
+ end
+ end
+
+ context 'with invalid params' do
+ it 'responds with 422 Unprocessable Entity' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+
+ patch(
+ api_v1_subscription_path(subscription),
+ params: { subscription: { interval_length: -1 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(422)
+ end
+ end
+ end
+
+ context 'when the subscription does not belong to the user' do
+ it 'responds with 401 Unauthorized' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription)
+
+ patch(
+ api_v1_subscription_path(subscription),
+ params: { subscription: { interval_length: 11 } },
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /:id/skip' do
+ context 'when the subscription belongs to the user' do
+ it 'responds with 200 OK' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+
+ post(
+ skip_api_v1_subscription_path(subscription),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(200)
+ end
+
+ it 'skips the subscription' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(
+ :subscription,
+ user: user,
+ interval_length: 1,
+ interval_units: 'week',
+ actionable_date: Time.zone.today,
+ )
+
+ post(
+ skip_api_v1_subscription_path(subscription),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(subscription.reload.actionable_date).to eq(Time.zone.today + 1.week)
+ end
+ end
+
+ context 'when the subscription does not belong to the user' do
+ it 'responds with 401 Unauthorized' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription)
+
+ post(
+ skip_api_v1_subscription_path(subscription),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /:id/cancel' do
+ context 'when the subscription belongs to the user' do
+ it 'responds with 200 OK' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+
+ post(
+ cancel_api_v1_subscription_path(subscription),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(200)
+ end
+
+ it 'cancels the subscription' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription, user: user)
+
+ post(
+ cancel_api_v1_subscription_path(subscription),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(subscription.reload.state).to eq('canceled')
+ end
+ end
+
+ context 'when the subscription does not belong to the user' do
+ it 'responds with 401 Unauthorized' do
+ user = create(:user, &:generate_spree_api_key!)
+ subscription = create(:subscription)
+
+ post(
+ cancel_api_v1_subscription_path(subscription),
+ headers: { 'Authorization' => "Bearer #{user.spree_api_key}" },
+ )
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb b/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb
deleted file mode 100644
index bab7b23..0000000
--- a/spec/requests/solidus_subscriptions/api/v1/subscriptions_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe "Subscription endpoints", type: :request do
- let(:json_resp) { JSON.parse(response.body) }
- let(:user) { create :user }
-
- before { user.generate_spree_api_key! }
-
- describe "#cancel" do
- let(:subscription) do
- create :subscription, :with_line_item, actionable_date: (Date.current + 1.month), user: user
- end
-
- it "returns the canceled record", :aggregate_failures do
- post solidus_subscriptions.cancel_api_v1_subscription_path(subscription), params: { token: user.spree_api_key }
- expect(json_resp["state"]).to eq "canceled"
- expect(json_resp["actionable_date"]).to be_nil
- end
-
- context 'when the miniumum notice has been past' do
- let(:subscription) do
- create :subscription, :with_line_item, actionable_date: Date.current, user: user
- end
-
- it "returns the record pending cancellation", :aggregate_failures do
- post solidus_subscriptions.cancel_api_v1_subscription_path(subscription), params: { token: user.spree_api_key }
- expect(json_resp["state"]).to eq "pending_cancellation"
- end
- end
- end
-
- describe "#skip" do
- let(:subscription) { create :subscription, :with_line_item, actionable_date: 1.day.from_now, user: user }
- let(:expected_date) { "2016-10-27T00:00:00.000Z" }
-
- before { Timecop.freeze(Date.parse("2016-09-26")) }
-
- after { Timecop.return }
-
- it "returns the updated record", :aggregate_failures do
- post solidus_subscriptions.skip_api_v1_subscription_path(subscription), params: { token: user.spree_api_key }
- expect(json_resp["actionable_date"]).to eq expected_date
- end
- end
-end
diff --git a/spec/support/cancancan.rb b/spec/support/cancancan.rb
new file mode 100644
index 0000000..7fdda0c
--- /dev/null
+++ b/spec/support/cancancan.rb
@@ -0,0 +1 @@
+require 'cancan/matchers'