diff options
author | Alessandro Desantis <desa.alessandro@gmail.com> | 2020-10-08 13:49:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-08 13:49:22 +0200 |
commit | 4bafb311fe180f3cc0bb38f01f24f2b487e8be8b (patch) | |
tree | 83940eec6d1e693cf43e4c3b95a6d2e8233b9cd8 | |
parent | fb0777566944c9deef6cbacd7a7eac335838746a (diff) | |
parent | 1f6e8d5747f6fa995fb18443d1e264dbd779d124 (diff) |
Merge pull request #158 from solidusio-contrib/aldesantis/guest-token
Enable authorization via guest tokens
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' |