From e1d50e5b9c74a169081376addfb89a7a7c02dae7 Mon Sep 17 00:00:00 2001 From: wvengen Date: Sat, 13 Oct 2018 16:27:24 +0200 Subject: [PATCH] API v1 group_order_articles endpoints --- .../user/group_order_articles_controller.rb | 108 +++++++++ app/models/group_order.rb | 8 + app/models/group_order_article.rb | 13 +- app/models/order.rb | 12 +- .../group_order_article_serializer.rb | 8 + config/initializers/doorkeeper.rb | 2 +- config/locales/en.yml | 1 + config/routes.rb | 2 + doc/swagger.v1.yml | 213 ++++++++++++++++++ spec/api/v1/swagger_spec.rb | 78 ++++++- spec/api/v1/user/group_order_articles_spec.rb | 211 +++++++++++++++++ 11 files changed, 646 insertions(+), 10 deletions(-) create mode 100644 app/controllers/api/v1/user/group_order_articles_controller.rb create mode 100644 app/serializers/group_order_article_serializer.rb create mode 100644 spec/api/v1/user/group_order_articles_spec.rb diff --git a/app/controllers/api/v1/user/group_order_articles_controller.rb b/app/controllers/api/v1/user/group_order_articles_controller.rb new file mode 100644 index 00000000..52807d99 --- /dev/null +++ b/app/controllers/api/v1/user/group_order_articles_controller.rb @@ -0,0 +1,108 @@ +class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController + include Concerns::CollectionScope + + before_action ->{ doorkeeper_authorize! 'group_orders:user' } + + before_action :require_ordergroup + before_action :require_minimum_balance, only: [:create, :update] # destroy is ok + before_action :require_enough_apples, only: [:create, :update] # destroy is ok + # @todo allow decreasing amounts when minimum balance isn't met + + def index + render_collection search_scope + end + + def show + goa = scope.find(params.require(:id)) + render_goa_with_oa(goa) + end + + def create + goa = nil + GroupOrderArticle.transaction do + oa = order_articles_scope_for_create.find(create_params.require(:order_article_id)) + go = current_ordergroup.group_orders.find_or_create_by!(order_id: oa.order_id) + goa = go.group_order_articles.create!(order_article: oa) + goa.update_quantities((create_params[:quantity] || 0).to_i, (create_params[:tolerance] || 0).to_i) + oa.update_results! + go.update_price! + go.update_attributes! updated_by: current_user + end + render_goa_with_oa(goa) + end + + def update + goa = nil + GroupOrderArticle.transaction do + goa = scope_for_update.includes(:group_order_article_quantities).find(params.require(:id)) + goa.update_quantities((update_params[:quantity] || goa.quantity).to_i, (update_params[:tolerance] || goa.tolerance).to_i) + goa.order_article.update_results! + goa.group_order.update_price! + goa.group_order.update_attributes! updated_by: current_user + end + render_goa_with_oa(goa) + end + + def destroy + goa = nil + GroupOrderArticle.transaction do + goa = scope_for_update.find(params.require(:id)) + goa.destroy! + goa.order_article.update_results! + goa.group_order.update_price! + goa.group_order.update_attributes! updated_by: current_user + end + render_goa_with_oa(nil, goa.order_article) + end + + private + + def max_per_page + nil + end + + def scope + GroupOrderArticle. + joins(:group_order). + includes(order_article: :article, group_order: :order). + where(group_orders: { ordergroup_id: current_ordergroup.id }) + end + + def scope_for_update + scope.references(order_article: { group_order: :order }).merge(Order.open) + end + + def order_articles_scope_for_create + OrderArticle.joins(:order).merge(Order.open) + end + + def create_params + params.require(:group_order_article).permit(:order_article_id, :quantity, :tolerance) + end + + def update_params + params.require(:group_order_article).permit(:quantity, :tolerance) + end + + def require_minimum_balance + minimum_balance = FoodsoftConfig[:minimum_balance] or return + if current_ordergroup.account_balance < minimum_balance + raise Api::Errors::PermissionRequired.new(t('application.controller.error_minimum_balance', min: minimum_balance)) + end + end + + def require_enough_apples + if current_ordergroup.not_enough_apples? + s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples, stop_ordering_under: FoodsoftConfig[:stop_ordering_under]) + raise Api::Errors::PermissionRequired.new(s) + end + end + + def render_goa_with_oa(goa, oa = goa.order_article) + data = {} + data[:group_order_article] = GroupOrderArticleSerializer.new(goa) if goa + data[:order_article] = OrderArticleSerializer.new(oa) if oa + + render json: data, root: nil + end +end diff --git a/app/models/group_order.rb b/app/models/group_order.rb index 626f3fbd..d4cc7477 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -21,6 +21,14 @@ class GroupOrder < ApplicationRecord scope :ordered, -> { includes(:ordergroup).order('groups.name') } + def self.ransackable_attributes(auth_object = nil) + %w(id price) + end + + def self.ransackable_associations(auth_object = nil) + %w(order group_order_articles) + end + # Generate some data for the javascript methods in ordering view def load_data data = {} diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb index 0b30af48..538a16ce 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -5,16 +5,25 @@ class GroupOrderArticle < ApplicationRecord belongs_to :group_order belongs_to :order_article - has_many :group_order_article_quantities, :dependent => :destroy + has_many :group_order_article_quantities, dependent: :destroy validates_presence_of :group_order, :order_article validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order validate :check_order_not_closed # don't allow changes to closed (aka settled) orders + validates :quantity, :tolerance, numericality: { only_integer: true, greater_than_or_equal_to: 0 } scope :ordered, -> { includes(:group_order => :ordergroup).order('groups.name') } localize_input_of :result + def self.ransackable_attributes(auth_object = nil) + %w(id quantity tolerance result) + end + + def self.ransackable_associations(auth_object = nil) + %w(order_article group_order) + end + # Setter used in group_order_article#new # We have to create an group_order, if the ordergroup wasn't involved in the order yet def ordergroup_id=(id) @@ -86,7 +95,7 @@ class GroupOrderArticle < ApplicationRecord # Check if something went terribly wrong and quantites have not been adjusted as desired. if (self.quantity != quantity || self.tolerance != tolerance) - raise 'Invalid state: unable to update GroupOrderArticle/-Quantities to desired quantities!' + raise ActiveRecord::RecordNotSaved.new('Unable to update GroupOrderArticle/-Quantities to desired quantities!', self) end # Remove zero-only items. diff --git a/app/models/order.rb b/app/models/order.rb index 82c22481..61619f69 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -30,9 +30,9 @@ class Order < ApplicationRecord # Finders scope :started, -> { where('starts <= ?', Time.now) } - scope :closed, -> { where(state: 'closed').order('ends DESC') } - scope :stockit, -> { where(supplier_id: nil).order('ends DESC') } - scope :recent, -> { order('starts DESC').limit(10) } + scope :closed, -> { where(state: 'closed').order(ends: :desc) } + scope :stockit, -> { where(supplier_id: nil).order(ends: :desc) } + scope :recent, -> { order(starts: :desc).limit(10) } scope :stock_group_order, -> { group_orders.where(ordergroup_id: nil).first } scope :with_invoice, -> { where.not(invoice: nil) } @@ -42,9 +42,9 @@ class Order < ApplicationRecord # So orders can # 1. ...only transition in one direction (e.g. an order that has been `finished` currently cannot be reopened) # 2. ...be set to `closed` when having the `finished` state. (`received` is optional) - scope :open, -> { where(state: 'open').order('ends DESC') } - scope :finished, -> { where(state: %w[finished received closed]).order('ends DESC') } - scope :finished_not_closed, -> { where(state: %w[finished received]).order('ends DESC') } + scope :open, -> { where(state: 'open').order(ends: :desc) } + scope :finished, -> { where(state: %w[finished received closed]).order(ends: :desc) } + scope :finished_not_closed, -> { where(state: %w[finished received]).order(ends: :desc) } # Allow separate inputs for date and time # with workaround for https://github.com/einzige/date_time_attribute/issues/14 diff --git a/app/serializers/group_order_article_serializer.rb b/app/serializers/group_order_article_serializer.rb new file mode 100644 index 00000000..704fcc23 --- /dev/null +++ b/app/serializers/group_order_article_serializer.rb @@ -0,0 +1,8 @@ +class GroupOrderArticleSerializer < ActiveModel::Serializer + attributes :id, :order_article_id + attributes :quantity, :tolerance, :result, :total_price + + def total_price + object.total_price.to_f + end +end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index aa44583e..958b9e9a 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -49,7 +49,7 @@ Doorkeeper.configure do # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes # default is a collection of read-only scopes - default_scopes 'config:user', 'finance:user', 'user:read', 'orders:read' + default_scopes 'config:user', 'finance:user', 'user:read', 'orders:read', 'group_orders:user' optional_scopes 'config:read', 'config:write', 'finance:read', 'finance:write', diff --git a/config/locales/en.yml b/config/locales/en.yml index 57ec4d3e..334b1e58 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -418,6 +418,7 @@ en: error_denied_sign_in: sign in as another user error_feature_disabled: This feature is currently disabled. error_members_only: This action is only available to members of the group! + error_minimum_balance: Sorry, your account balance is below the minimum of %{min}. error_token: Access denied (invalid token)! article_categories: create: diff --git a/config/routes.rb b/config/routes.rb index 88391ada..7789e4d7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -266,6 +266,7 @@ Rails.application.routes.draw do namespace :user do root to: 'users#show' resources :financial_transactions, only: [:index, :show] + resources :group_order_articles end resources :financial_transaction_classes, only: [:index, :show] @@ -273,6 +274,7 @@ Rails.application.routes.draw do resources :financial_transactions, only: [:index, :show] resources :orders, only: [:index, :show] resources :order_articles, only: [:index, :show] + resources :group_order_articles end end diff --git a/doc/swagger.v1.yml b/doc/swagger.v1.yml index ca2278b5..131e2e20 100644 --- a/doc/swagger.v1.yml +++ b/doc/swagger.v1.yml @@ -120,6 +120,177 @@ paths: security: - foodsoft_auth: ['finance:user'] + /user/group_order_articles: + get: + summary: group order articles + tags: + - 1. User + - 2. Order + parameters: + - $ref: '#/parameters/page' + - $ref: '#/parameters/per_page' + - $ref: '#/parameters/q_ordered' + responses: + 200: + description: success + schema: + type: object + properties: + group_order_articles: + type: array + items: + $ref: '#/definitions/GroupOrderArticle' + meta: + $ref: '#/definitions/Meta' + 401: + description: not logged-in + schema: + $ref: '#/definitions/Error401' + 403: + description: user has no ordergroup or missing scope + schema: + $ref: '#/definitions/Error403' + security: + - foodsoft_auth: ['group_orders:user'] + post: + summary: create new group order article + tags: + - 1. User + - 2. Order + parameters: + - in: body + name: body + description: group order article to create + required: true + schema: + $ref: '#/definitions/GroupOrderArticleForCreate' + responses: + 200: + description: success + schema: + type: object + properties: + group_order_article: + $ref: '#/definitions/GroupOrderArticle' + order_article: + $ref: '#/definitions/OrderArticle' + 401: + description: not logged-in + schema: + $ref: '#/definitions/Error401' + 403: + description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope + schema: + $ref: '#/definitions/Error403' + 404: + description: order article not found in open orders + schema: + $ref: '#/definitions/Error404' + 422: + description: invalid parameter value or group order article already exists + schema: + $ref: '#/definitions/Error422' + /user/group_order_articles/{id}: + parameters: + - $ref: '#/parameters/idInUrl' + get: + summary: find group order article by id + tags: + - 1. User + - 2. Order + responses: + 200: + description: success + schema: + type: object + properties: + group_order_article: + $ref: '#/definitions/GroupOrderArticle' + order_article: + $ref: '#/definitions/OrderArticle' + 401: + description: not logged-in + schema: + $ref: '#/definitions/Error401' + 403: + description: user has no ordergroup or missing scope + schema: + $ref: '#/definitions/Error403' + 404: + description: not found + schema: + $ref: '#/definitions/Error404' + security: + - foodsoft_auth: ['group_orders:user'] + patch: + summary: update a group order article (but delete if quantity and tolerance are zero) + tags: + - 1. User + - 2. Order + parameters: + - $ref: '#/parameters/idInUrl' + - in: body + name: body + description: group order article update + required: true + schema: + $ref: '#/definitions/GroupOrderArticleForUpdate' + responses: + 200: + description: success + schema: + type: object + properties: + group_order_article: + $ref: '#/definitions/GroupOrderArticle' + order_article: + $ref: '#/definitions/OrderArticle' + 401: + description: not logged-in + schema: + $ref: '#/definitions/Error401' + 403: + description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope + schema: + $ref: '#/definitions/Error403' + 404: + description: order article not found in open orders + schema: + $ref: '#/definitions/Error404' + 422: + description: invalid parameter value + schema: + $ref: '#/definitions/Error422' + delete: + summary: remove group order article + tags: + - 1. User + - 2. Order + parameters: + - $ref: '#/parameters/idInUrl' + responses: + 200: + description: success + schema: + type: object + properties: + group_order_article: + $ref: '#/definitions/GroupOrderArticle' + order_article: + $ref: '#/definitions/OrderArticle' + 401: + description: not logged-in + schema: + $ref: '#/definitions/Error401' + 403: + description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope + schema: + $ref: '#/definitions/Error403' + 404: + description: order article not found in open orders + schema: + $ref: '#/definitions/Error404' + /financial_transactions: get: summary: financial transactions @@ -654,6 +825,39 @@ definitions: article: $ref: '#/definitions/Article' + GroupOrderArticleForUpdate: + type: object + properties: + quantity: + type: integer + description: number of units ordered by the user's ordergroup + tolerance: + type: integer + description: number of extra units the user's ordergroup is willing to buy for filling a box + GroupOrderArticleForCreate: + allOf: + - $ref: '#/definitions/GroupOrderArticleForUpdate' + - type: object + properties: + order_article_id: + type: integer + description: id of order article + GroupOrderArticle: + allOf: + - $ref: '#/definitions/GroupOrderArticleForCreate' + - type: object + properties: + id: + type: integer + result: + type: number + format: float + description: number of units the user's ordergroup will receive or has received + total_price: + type: number + format: float + description: total price of this group order article + Navigation: type: array items: @@ -721,6 +925,15 @@ definitions: description: 'forbidden or invalid_scope' error_description: $ref: '#/definitions/Error/properties/error_description' + Error422: + type: object + properties: + error: + type: string + description: unprocessable entity + error_description: + $ref: '#/definitions/Error/properties/error_description' + securityDefinitions: foodsoft_auth: diff --git a/spec/api/v1/swagger_spec.rb b/spec/api/v1/swagger_spec.rb index 74404756..c4031300 100644 --- a/spec/api/v1/swagger_spec.rb +++ b/spec/api/v1/swagger_spec.rb @@ -27,7 +27,7 @@ describe 'API v1', type: :apivore, order: :defined do it_handles_invalid_token_and_scope(:get, '/user') end - context 'financial_transactions' do + context 'user/financial_transactions' do let(:api_scopes) { ['finance:user'] } let(:other_user) { create :user, :ordergroup } let!(:other_ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup } @@ -53,6 +53,82 @@ describe 'API v1', type: :apivore, order: :defined do end end + context 'user/group_order_articles' do + let(:api_scopes) { ['group_orders:user'] } + let(:order) { create(:order, article_count: 2) } + + let(:user_2) { create :user, :ordergroup } + let(:group_order_2) { create(:group_order, order: order, ordergroup: user_2.ordergroup) } + let!(:goa_2) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order_2 } + before { group_order_2.update_price!; user_2.ordergroup.update_stats! } + + context 'without ordergroup' do + it { is_expected.to validate(:get, '/user/group_order_articles', 403, api_auth) } + it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 403, api_auth({'id' => goa_2.id})) } + end + + context 'with ordergroup' do + let(:user) { create :user, :ordergroup } + let(:group_order) { create(:group_order, order: order, ordergroup: user.ordergroup) } + let!(:goa) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order } + before { group_order.update_price!; user.ordergroup.update_stats! } + + it { is_expected.to validate(:get, '/user/group_order_articles', 200, api_auth) } + it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) } + it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({'id' => goa_2.id})) } + it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({'id' => GroupOrderArticle.last.id + 1})) } + + let(:create_params) { {'_data' => {group_order_article: {order_article_id: order.order_articles[1].id, quantity: 1}}} } + let(:update_params) { {'id' => goa.id, '_data' => {group_order_article: {quantity: goa.quantity + 1, tolerance: 0}}} } + + it { is_expected.to validate(:post, '/user/group_order_articles', 200, api_auth(create_params)) } + it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 200, api_auth(update_params)) } + it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) } + + context 'with an existing group_order_article' do + let(:create_params) { {'_data' => {group_order_article: {order_article_id: order.order_articles[0].id, quantity: 1}}} } + + it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) } + end + + context 'with invalid parameter values' do + let(:create_params) { {'_data' => {group_order_article: {order_article_id: order.order_articles[0].id, quantity: -1}}} } + let(:update_params) { {'id' => goa.id, '_data' => {group_order_article: {quantity: -1, tolerance: 0}}} } + + it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) } + it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 422, api_auth(update_params)) } + end + + context 'with a closed order' do + let(:order) { create(:order, article_count: 2, state: :finished) } + + it { is_expected.to validate(:post, '/user/group_order_articles', 404, api_auth(create_params)) } + it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 404, api_auth(update_params)) } + it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 404, api_auth({'id' => goa.id})) } + end + + context 'without enough balance' do + before { FoodsoftConfig[:minimum_balance] = 1000 } + it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) } + it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) } + it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) } + end + + context 'without enough apple points' do + before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } + it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) } + it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) } + it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) } + end + + it_handles_invalid_token_and_scope(:get, '/user/group_order_articles') + it_handles_invalid_token_and_scope(:post, '/user/group_order_articles', ->{ api_auth(create_params) }) + it_handles_invalid_token_and_scope(:get, '/user/group_order_articles/{id}', ->{ api_auth({'id' => goa.id}) }) + it_handles_invalid_token_and_scope(:patch, '/user/group_order_articles/{id}', ->{ api_auth(update_params) }) + it_handles_invalid_token_and_scope(:delete, '/user/group_order_articles/{id}', ->{ api_auth({'id' => goa.id}) }) + end + end + context 'config' do let(:api_scopes) { ['config:user'] } diff --git a/spec/api/v1/user/group_order_articles_spec.rb b/spec/api/v1/user/group_order_articles_spec.rb new file mode 100644 index 00000000..8b940813 --- /dev/null +++ b/spec/api/v1/user/group_order_articles_spec.rb @@ -0,0 +1,211 @@ +require 'spec_helper' + +# Most routes are tested in the swagger_spec, this tests endpoints that change data. +describe Api::V1::User::GroupOrderArticlesController, type: :controller do + include ApiOAuth + let(:user) { create(:user, :ordergroup) } + let(:api_scopes) { ['group_orders:user'] } + + let(:order) { create(:order, article_count: 1) } + let(:oa_1) { order.order_articles.first } + + let(:other_quantity) { rand(1..10) } + let(:other_tolerance) { rand(1..10) } + let(:user_other) { create(:user, :ordergroup) } + let!(:go_other) { create(:group_order, order: order, ordergroup: user_other.ordergroup ) } + let!(:goa_other) { create(:group_order_article, group_order: go_other, order_article: oa_1, quantity: other_quantity, tolerance: other_tolerance) } + before { go_other.update_price!; user_other.ordergroup.update_stats! } + + let(:json_goa) { json_response['group_order_article'] } + let(:json_oa) { json_response['order_article'] } + + + shared_examples "group_order_articles endpoint success" do + before { request } + + it "returns status 200" do + expect(response.status).to eq 200 + end + + it "returns the order_article" do + expect(json_oa['id']).to eq oa_1.id + expect(json_oa['quantity']).to eq new_quantity + other_quantity + expect(json_oa['tolerance']).to eq new_tolerance + other_tolerance + end + + it "updates the group_order" do + go = nil + expect { + request + go = user.ordergroup.group_orders.where(order: order).last + }.to change { go&.updated_by }.to(user) + .and change { go&.price } + end + end + + shared_examples "group_order_articles create/update success" do + include_examples "group_order_articles endpoint success" + + it "returns the group_order_article" do + expect(json_goa['id']).to be_present + expect(json_goa['order_article_id']).to eq oa_1.id + expect(json_goa['quantity']).to eq new_quantity + expect(json_goa['tolerance']).to eq new_tolerance + end + + it "updates the group_order_article" do + resulting_goa = GroupOrderArticle.where(id: json_goa['id']).first + expect(resulting_goa).to be_present + expect(resulting_goa.quantity).to eq new_quantity + expect(resulting_goa.tolerance).to eq new_tolerance + end + end + + + shared_examples "group_order_articles endpoint failure" do |status| + it "returns status #{status}" do + request + expect(response.status).to eq status + end + + it "does not change the group_order" do + expect { request }.to_not change { + go = user.ordergroup.group_orders.where(order: order).last + go&.attributes + } + end + + it "does not change the group_order_article" do + expect { request }.to_not change { + goa = GroupOrderArticle.joins(:group_order) + .where(order_article_id: oa_1.id, group_orders: { ordergroup: user.ordergroup }).last + goa&.attributes + } + end + end + + + describe "POST :create" do + let(:new_quantity) { rand(1..10) } + let(:new_tolerance) { rand(1..10) } + + let(:goa_params) { { order_article_id: oa_1.id, quantity: new_quantity, tolerance: new_tolerance } } + let(:request) { post :create, params: { group_order_article: goa_params, foodcoop: 'f' } } + + context "with no existing group_order" do + include_examples "group_order_articles create/update success" + end + + context "with an existing group_order" do + let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } + include_examples "group_order_articles create/update success" + end + + context "with an existing group_order_article" do + let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } + let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 0, tolerance: 1) } + before { go.update_price!; user.ordergroup.update_stats! } + include_examples "group_order_articles endpoint failure", 422 + end + + context "with invalid parameter values" do + let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance} } + include_examples "group_order_articles endpoint failure", 422 + end + + context 'with a closed order' do + let(:order) { create(:order, article_count: 1, state: :finished) } + include_examples "group_order_articles endpoint failure", 404 + end + + context 'without enough balance' do + before { FoodsoftConfig[:minimum_balance] = 1000 } + include_examples "group_order_articles endpoint failure", 403 + end + + context 'without enough apple points' do + before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } + include_examples "group_order_articles endpoint failure", 403 + end + end + + describe "PATCH :update" do + let(:new_quantity) { rand(2..10) } + let(:new_tolerance) { rand(2..10) } + + let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } + let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 1, tolerance: 0) } + before { go.update_price!; user.ordergroup.update_stats! } + + let(:goa_params) { { quantity: new_quantity, tolerance: new_tolerance } } + let(:request) { patch :update, params: { id: goa.id, group_order_article: goa_params, foodcoop: 'f' } } + + context "happy flow" do + include_examples "group_order_articles create/update success" + end + + context "with invalid parameter values" do + let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance} } + include_examples "group_order_articles endpoint failure", 422 + end + + context 'with a closed order' do + let(:order) { create(:order, article_count: 1, state: :finished) } + include_examples "group_order_articles endpoint failure", 404 + end + + context 'without enough balance' do + before { FoodsoftConfig[:minimum_balance] = 1000 } + include_examples "group_order_articles endpoint failure", 403 + end + + context 'without enough apple points' do + before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } + include_examples "group_order_articles endpoint failure", 403 + end + end + + describe "DELETE :destroy" do + let(:new_quantity) { 0 } + let(:new_tolerance) { 0 } + + let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } + let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1) } + before { go.update_price!; user.ordergroup.update_stats! } + + let(:request) { delete :destroy, params: { id: goa.id, foodcoop: 'f' } } + + + shared_examples "group_order_articles destroy success" do + include_examples "group_order_articles endpoint success" + + it "does not return the group_order_article" do + expect(json_goa).to be_nil + end + + it "deletes the group_order_article" do + expect(GroupOrderArticle.where(id: goa.id)).to be_empty + end + end + + + context "happy flow" do + include_examples "group_order_articles destroy success" + end + + context 'with a closed order' do + let(:order) { create(:order, article_count: 1, state: :finished) } + include_examples "group_order_articles endpoint failure", 404 + end + + context 'without enough balance' do + before { FoodsoftConfig[:minimum_balance] = 1000 } + include_examples "group_order_articles destroy success" + end + + context 'without enough apple points' do + before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } + include_examples "group_order_articles destroy success" + end + end +end