Replace apivore with rswag for api tests (#969)
* Replace apivore api tests with rswag * move to OpenAPI Spec 3.0.1 * a swagger UI is now reachable at http://localhost:3000/api-docs/index.html * swagger file is generated by running `RAILS_ENV=test rails rswag` and it was moved from /docs/swagger.v1.yml to /swagger/v1/swagger.yml --------- Co-authored-by: viehlieb <pf@pragma-shift.net>
This commit is contained in:
parent
8604e27fe9
commit
c67e9b5be8
27 changed files with 1478 additions and 1858 deletions
|
@ -451,6 +451,15 @@ RSpec/DescribedClass:
|
||||||
- "spec/models/ordergroup_spec.rb"
|
- "spec/models/ordergroup_spec.rb"
|
||||||
- "spec/models/user_spec.rb"
|
- "spec/models/user_spec.rb"
|
||||||
|
|
||||||
|
# Offense count: 15
|
||||||
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
|
RSpec/EmptyExampleGroup:
|
||||||
|
Exclude:
|
||||||
|
# exclude for rswag tests:
|
||||||
|
- 'spec/requests/api/**/*_spec.rb'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Offense count: 65
|
# Offense count: 65
|
||||||
# Configuration parameters: CountAsOne.
|
# Configuration parameters: CountAsOne.
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
|
@ -581,6 +590,14 @@ RSpec/ScatteredSetup:
|
||||||
- "spec/integration/balancing_spec.rb"
|
- "spec/integration/balancing_spec.rb"
|
||||||
- "spec/integration/login_spec.rb"
|
- "spec/integration/login_spec.rb"
|
||||||
|
|
||||||
|
# Offense count: 4
|
||||||
|
# Configuration parameters: AllowedPatterns, IgnoredPatterns.
|
||||||
|
# SupportedStyles: snake_case, camelCase
|
||||||
|
RSpec/VariableName:
|
||||||
|
EnforcedStyle: snake_case
|
||||||
|
AllowedPatterns:
|
||||||
|
- ^Authorization$
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
|
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
|
||||||
RSpec/VerifiedDoubles:
|
RSpec/VerifiedDoubles:
|
||||||
|
|
6
Gemfile
6
Gemfile
|
@ -55,6 +55,9 @@ gem 'gaffe'
|
||||||
gem 'ruby-filemagic'
|
gem 'ruby-filemagic'
|
||||||
gem 'mime-types'
|
gem 'mime-types'
|
||||||
gem 'midi-smtp-server'
|
gem 'midi-smtp-server'
|
||||||
|
gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114
|
||||||
|
gem 'rswag-api'
|
||||||
|
gem 'rswag-ui'
|
||||||
|
|
||||||
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
||||||
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
|
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
|
||||||
|
@ -116,6 +119,5 @@ group :test do
|
||||||
gem 'simplecov', require: false
|
gem 'simplecov', require: false
|
||||||
gem 'simplecov-lcov', require: false
|
gem 'simplecov-lcov', require: false
|
||||||
# api
|
# api
|
||||||
gem 'apivore', require: false
|
gem 'rswag-specs'
|
||||||
gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114
|
|
||||||
end
|
end
|
||||||
|
|
21
Gemfile.lock
21
Gemfile.lock
|
@ -109,13 +109,6 @@ GEM
|
||||||
activerecord (>= 3.0.0)
|
activerecord (>= 3.0.0)
|
||||||
addressable (2.8.1)
|
addressable (2.8.1)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
apivore (1.6.2)
|
|
||||||
actionpack (>= 4, < 6)
|
|
||||||
hashie (~> 3.3)
|
|
||||||
json-schema (~> 2.5)
|
|
||||||
rspec (~> 3)
|
|
||||||
rspec-expectations (~> 3.1)
|
|
||||||
rspec-mocks (~> 3.1)
|
|
||||||
apparition (0.6.0)
|
apparition (0.6.0)
|
||||||
capybara (~> 3.13, < 4)
|
capybara (~> 3.13, < 4)
|
||||||
websocket-driver (>= 0.6.5)
|
websocket-driver (>= 0.6.5)
|
||||||
|
@ -430,6 +423,16 @@ GEM
|
||||||
rspec-rerun (1.1.0)
|
rspec-rerun (1.1.0)
|
||||||
rspec (~> 3.0)
|
rspec (~> 3.0)
|
||||||
rspec-support (3.11.1)
|
rspec-support (3.11.1)
|
||||||
|
rswag-api (2.7.0)
|
||||||
|
railties (>= 3.1, < 7.1)
|
||||||
|
rswag-specs (2.7.0)
|
||||||
|
activesupport (>= 3.1, < 7.1)
|
||||||
|
json-schema (>= 2.2, < 4.0)
|
||||||
|
railties (>= 3.1, < 7.1)
|
||||||
|
rspec-core (>= 2.14)
|
||||||
|
rswag-ui (2.7.0)
|
||||||
|
actionpack (>= 3.1, < 7.1)
|
||||||
|
railties (>= 3.1, < 7.1)
|
||||||
rubocop (1.36.0)
|
rubocop (1.36.0)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
|
@ -557,7 +560,6 @@ DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10.0)
|
active_model_serializers (~> 0.10.0)
|
||||||
acts_as_tree
|
acts_as_tree
|
||||||
acts_as_versioned!
|
acts_as_versioned!
|
||||||
apivore
|
|
||||||
apparition
|
apparition
|
||||||
attribute_normalizer
|
attribute_normalizer
|
||||||
better_errors
|
better_errors
|
||||||
|
@ -617,6 +619,9 @@ DEPENDENCIES
|
||||||
rspec-core
|
rspec-core
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rspec-rerun
|
rspec-rerun
|
||||||
|
rswag-api
|
||||||
|
rswag-specs
|
||||||
|
rswag-ui
|
||||||
rubocop
|
rubocop
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
|
|
13
config/initializers/rswag_api.rb
Normal file
13
config/initializers/rswag_api.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Rswag::Api.configure do |c|
|
||||||
|
# Specify a root folder where Swagger JSON files are located
|
||||||
|
# This is used by the Swagger middleware to serve requests for API descriptions
|
||||||
|
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
|
||||||
|
# that it's configured to generate files in the same folder
|
||||||
|
c.swagger_root = Rails.root.to_s + '/swagger'
|
||||||
|
|
||||||
|
# Inject a lambda function to alter the returned Swagger prior to serialization
|
||||||
|
# The function will have access to the rack env for the current request
|
||||||
|
# For example, you could leverage this to dynamically assign the "host" property
|
||||||
|
#
|
||||||
|
# c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
|
||||||
|
end
|
15
config/initializers/rswag_ui.rb
Normal file
15
config/initializers/rswag_ui.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
Rswag::Ui.configure do |c|
|
||||||
|
# List the Swagger endpoints that you want to be documented through the
|
||||||
|
# swagger-ui. The first parameter is the path (absolute or relative to the UI
|
||||||
|
# host) to the corresponding endpoint and the second is a title that will be
|
||||||
|
# displayed in the document selector.
|
||||||
|
# NOTE: If you're using rspec-api to expose Swagger files
|
||||||
|
# (under swagger_root) as JSON or YAML endpoints, then the list below should
|
||||||
|
# correspond to the relative paths for those endpoints.
|
||||||
|
|
||||||
|
c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs'
|
||||||
|
|
||||||
|
# Add Basic Auth in case your API is private
|
||||||
|
# c.basic_auth_enabled = true
|
||||||
|
# c.basic_auth_credentials 'username', 'password'
|
||||||
|
end
|
|
@ -1,4 +1,7 @@
|
||||||
|
# rubocop:disable Metrics/BlockLength
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
|
mount Rswag::Ui::Engine => '/api-docs'
|
||||||
|
mount Rswag::Api::Engine => '/api-docs'
|
||||||
get "order_comments/new"
|
get "order_comments/new"
|
||||||
|
|
||||||
get "comments/new"
|
get "comments/new"
|
||||||
|
@ -290,3 +293,4 @@ Rails.application.routes.draw do
|
||||||
resources :users, only: [:index]
|
resources :users, only: [:index]
|
||||||
end # End of /:foodcoop scope
|
end # End of /:foodcoop scope
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics/BlockLength
|
||||||
|
|
|
@ -5,9 +5,11 @@ like listing open orders, updating the ordergroup's order, and listing financial
|
||||||
transactions. Not all Foodsoft functionality is available through the API, but
|
transactions. Not all Foodsoft functionality is available through the API, but
|
||||||
we're open for new additions.
|
we're open for new additions.
|
||||||
|
|
||||||
The API is documented using [Open API 2.0](https://github.com/OAI/OpenAPI-Specification)
|
The API is documented using [Open API 3.0.1](https://github.com/OAI/OpenAPI-Specification)
|
||||||
/ [Swagger](https://swagger.io/) in [swagger.v1.yml](swagger.v1.yml).
|
/ [Swagger](https://swagger.io/) in [swagger.yaml](/swagger/v1/swagger.yaml).
|
||||||
This provides a machine-readable reference that is used to provide documentation.
|
This provides a machine-readable reference that is used to provide documentation.
|
||||||
|
It is generated by [rswag](https://github.com/rswag) wich also provides api-tests.
|
||||||
|
It can be generated running `RAILS_ENV=test rails rswag`.
|
||||||
|
|
||||||
**Note:** the current OAuth scopes may be subject to change, until the next release of Foodsoft.
|
**Note:** the current OAuth scopes may be subject to change, until the next release of Foodsoft.
|
||||||
|
|
||||||
|
|
1106
doc/swagger.v1.yml
1106
doc/swagger.v1.yml
File diff suppressed because it is too large
Load diff
|
@ -1,59 +0,0 @@
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
# Most routes are tested in the swagger_spec, this tests (non-ransack) parameters.
|
|
||||||
describe Api::V1::OrderArticlesController, type: :controller do
|
|
||||||
include ApiOAuth
|
|
||||||
let(:api_scopes) { ['orders:read'] }
|
|
||||||
|
|
||||||
let(:json_order_articles) { json_response['order_articles'] }
|
|
||||||
let(:json_order_article_ids) { json_order_articles.map { |joa| joa["id"] } }
|
|
||||||
|
|
||||||
describe "GET :index" do
|
|
||||||
context "with param q[ordered]" do
|
|
||||||
let(:order) { create(:order, article_count: 4) }
|
|
||||||
let(:order_articles) { order.order_articles }
|
|
||||||
|
|
||||||
before do
|
|
||||||
order_articles[0].update!(quantity: 0, tolerance: 0, units_to_order: 0)
|
|
||||||
order_articles[1].update!(quantity: 1, tolerance: 0, units_to_order: 0)
|
|
||||||
order_articles[2].update!(quantity: 0, tolerance: 1, units_to_order: 0)
|
|
||||||
order_articles[3].update!(quantity: 0, tolerance: 0, units_to_order: 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "(unset)" do
|
|
||||||
get :index, params: { foodcoop: 'f' }
|
|
||||||
expect(json_order_articles.count).to eq 4
|
|
||||||
end
|
|
||||||
|
|
||||||
it "all" do
|
|
||||||
get :index, params: { foodcoop: 'f', q: { ordered: 'all' } }
|
|
||||||
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "supplier" do
|
|
||||||
get :index, params: { foodcoop: 'f', q: { ordered: 'supplier' } }
|
|
||||||
expect(json_order_article_ids).to match_array [order_articles[3].id]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "member" do
|
|
||||||
get :index, params: { foodcoop: 'f', q: { ordered: 'member' } }
|
|
||||||
expect(json_order_articles.count).to eq 0
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when ordered by user" do
|
|
||||||
let(:user) { create(:user, :ordergroup) }
|
|
||||||
let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
create(:group_order_article, group_order: go, order_article: order_articles[1], quantity: 1)
|
|
||||||
create(:group_order_article, group_order: go, order_article: order_articles[2], tolerance: 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "member" do
|
|
||||||
get :index, params: { foodcoop: 'f', q: { ordered: 'member' } }
|
|
||||||
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,284 +0,0 @@
|
||||||
require 'spec_helper'
|
|
||||||
require 'apivore'
|
|
||||||
|
|
||||||
# we want to load a local file in YAML-format instead of a served JSON file
|
|
||||||
class SwaggerCheckerFile < Apivore::SwaggerChecker
|
|
||||||
def fetch_swagger!
|
|
||||||
YAML.load(File.read(swagger_path))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'API v1', type: :apivore, order: :defined do
|
|
||||||
include ApiHelper
|
|
||||||
|
|
||||||
subject { SwaggerCheckerFile.instance_for Rails.root.join('doc', 'swagger.v1.yml') }
|
|
||||||
|
|
||||||
context 'has valid paths' do
|
|
||||||
context 'user' do
|
|
||||||
let(:api_scopes) { ['user:read'] }
|
|
||||||
# create multiple users to make sure we're getting the authenticated user, not just any
|
|
||||||
let!(:other_user_1) { create :user }
|
|
||||||
let!(:user) { create :user }
|
|
||||||
let!(:other_user_2) { create :user }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/user', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/user', 401) }
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/user')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'user/financial_overview' do
|
|
||||||
let(:api_scopes) { ['finance:user'] }
|
|
||||||
let!(:user) { create :user, :ordergroup }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_overview', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_overview', 401) }
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/user/financial_overview')
|
|
||||||
end
|
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
||||||
context 'without ordergroup' do
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions', 403, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 403, api_auth({ 'id' => other_ft_1.id })) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with ordergroup' do
|
|
||||||
let(:user) { create :user, :ordergroup }
|
|
||||||
let!(:ft_1) { create :financial_transaction, ordergroup: user.ordergroup }
|
|
||||||
let!(:ft_2) { create :financial_transaction, ordergroup: user.ordergroup }
|
|
||||||
let!(:ft_3) { create :financial_transaction, ordergroup: user.ordergroup }
|
|
||||||
|
|
||||||
let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id, note: 'note' } } } }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 200, api_auth({ 'id' => ft_2.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => other_ft_1.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => FinancialTransaction.last.id + 1 })) }
|
|
||||||
|
|
||||||
context 'without using self service' do
|
|
||||||
it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with using self service' do
|
|
||||||
before { FoodsoftConfig[:use_self_service] = true }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:post, '/user/financial_transactions', 200, api_auth(create_params)) }
|
|
||||||
|
|
||||||
context 'with invalid financial transaction type' do
|
|
||||||
let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: -1, note: 'note' } } } }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:post, '/user/financial_transactions', 404, api_auth(create_params)) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without note' do
|
|
||||||
let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id } } } }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:post, '/user/financial_transactions', 422, api_auth(create_params)) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without enough balance' do
|
|
||||||
before { FoodsoftConfig[:minimum_balance] = 1000 }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions')
|
|
||||||
it_handles_invalid_token_and_scope(:post, '/user/financial_transactions', -> { api_auth(create_params) })
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions/{id}', -> { api_auth('id' => ft_2.id) })
|
|
||||||
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(:update_params) { { 'id' => goa.id, '_data' => { group_order_article: { quantity: goa.quantity + 1, tolerance: 0 } } } }
|
|
||||||
let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[1].id, quantity: 1 } } } }
|
|
||||||
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 })) }
|
|
||||||
|
|
||||||
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'] }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/config', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/config', 401) }
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/config')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'navigation' do
|
|
||||||
it { is_expected.to validate(:get, '/navigation', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/navigation', 401) }
|
|
||||||
|
|
||||||
it_handles_invalid_token(:get, '/navigation')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'financial_transactions' do
|
|
||||||
let(:api_scopes) { ['finance:read'] }
|
|
||||||
let(:user) { create(:user, :role_finance) }
|
|
||||||
let(:other_user) { create :user, :ordergroup }
|
|
||||||
let!(:ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup }
|
|
||||||
let!(:ft_2) { create :financial_transaction, ordergroup: other_user.ordergroup }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/financial_transactions', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transactions/{id}', 200, api_auth({ 'id' => ft_2.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transactions/{id}', 404, api_auth({ 'id' => FinancialTransaction.last.id + 1 })) }
|
|
||||||
|
|
||||||
context 'without role_finance' do
|
|
||||||
let(:user) { create(:user) }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/financial_transactions', 403, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transactions/{id}', 403, api_auth({ 'id' => ft_2.id })) }
|
|
||||||
end
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/financial_transactions')
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/financial_transactions/{id}', -> { api_auth({ 'id' => ft_2.id }) })
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'financial_transaction_classes' do
|
|
||||||
let!(:cla_1) { create :financial_transaction_class }
|
|
||||||
let!(:cla_2) { create :financial_transaction_class }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/financial_transaction_classes', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transaction_classes/{id}', 200, api_auth({ 'id' => cla_2.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transaction_classes/{id}', 404, api_auth({ 'id' => cla_2.id + 1 })) }
|
|
||||||
|
|
||||||
it_handles_invalid_token(:get, '/financial_transaction_classes')
|
|
||||||
it_handles_invalid_token(:get, '/financial_transaction_classes/{id}', -> { api_auth({ 'id' => cla_1.id }) })
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'financial_transaction_types' do
|
|
||||||
let!(:tpy_1) { create :financial_transaction_type }
|
|
||||||
let!(:tpy_2) { create :financial_transaction_type }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/financial_transaction_types', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transaction_types/{id}', 200, api_auth({ 'id' => tpy_2.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/financial_transaction_types/{id}', 404, api_auth({ 'id' => tpy_2.id + 1 })) }
|
|
||||||
|
|
||||||
it_handles_invalid_token(:get, '/financial_transaction_types')
|
|
||||||
it_handles_invalid_token(:get, '/financial_transaction_types/{id}', -> { api_auth({ 'id' => tpy_1.id }) })
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'orders' do
|
|
||||||
let(:api_scopes) { ['orders:read'] }
|
|
||||||
let!(:order) { create :order }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/orders', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/orders/{id}', 200, api_auth({ 'id' => order.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/orders/{id}', 404, api_auth({ 'id' => Order.last.id + 1 })) }
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/orders')
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/orders/{id}', -> { api_auth({ 'id' => order.id }) })
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'order_articles' do
|
|
||||||
let(:api_scopes) { ['orders:read'] }
|
|
||||||
let!(:order_article) { create(:order, article_count: 1).order_articles.first }
|
|
||||||
let!(:stock_article) { create(:stock_article) }
|
|
||||||
let!(:stock_order_article) { create(:stock_order, article_ids: [stock_article.id]).order_articles.first }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/order_articles', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/order_articles/{id}', 200, api_auth({ 'id' => order_article.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/order_articles/{id}', 200, api_auth({ 'id' => stock_order_article.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/order_articles/{id}', 404, api_auth({ 'id' => Article.last.id + 1 })) }
|
|
||||||
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/order_articles')
|
|
||||||
it_handles_invalid_token_and_scope(:get, '/order_articles/{id}', -> { api_auth({ 'id' => order_article.id }) })
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'article_categories' do
|
|
||||||
let!(:cat_1) { create :article_category }
|
|
||||||
let!(:cat_2) { create :article_category }
|
|
||||||
|
|
||||||
it { is_expected.to validate(:get, '/article_categories', 200, api_auth) }
|
|
||||||
it { is_expected.to validate(:get, '/article_categories/{id}', 200, api_auth({ 'id' => cat_2.id })) }
|
|
||||||
it { is_expected.to validate(:get, '/article_categories/{id}', 404, api_auth({ 'id' => cat_2.id + 1 })) }
|
|
||||||
|
|
||||||
it_handles_invalid_token(:get, '/article_categories')
|
|
||||||
it_handles_invalid_token(:get, '/article_categories/{id}', -> { api_auth({ 'id' => cat_1.id }) })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# needs to be last context so it is always run at the end
|
|
||||||
context 'and finally' do
|
|
||||||
it 'tests all documented routes' do
|
|
||||||
is_expected.to validate_all_paths
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,109 +0,0 @@
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
# Most routes are tested in the swagger_spec, this tests endpoints that change data.
|
|
||||||
describe Api::V1::User::FinancialTransactionsController, type: :controller do
|
|
||||||
include ApiOAuth
|
|
||||||
let(:user) { create(:user, :ordergroup) }
|
|
||||||
let(:api_scopes) { ['finance:user'] }
|
|
||||||
|
|
||||||
let(:ftc1) { create :financial_transaction_class }
|
|
||||||
let(:ftc2) { create :financial_transaction_class }
|
|
||||||
let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 }
|
|
||||||
let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 }
|
|
||||||
let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 }
|
|
||||||
|
|
||||||
let(:amount) { rand(-100..100) }
|
|
||||||
let(:note) { Faker::Lorem.sentence }
|
|
||||||
|
|
||||||
let(:json_ft) { json_response['financial_transaction'] }
|
|
||||||
|
|
||||||
shared_examples "financial_transactions endpoint success" do
|
|
||||||
before { request }
|
|
||||||
|
|
||||||
it "returns status 200" do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples "financial_transactions create/update success" do
|
|
||||||
include_examples "financial_transactions endpoint success"
|
|
||||||
|
|
||||||
it "returns the financial_transaction" do
|
|
||||||
expect(json_ft['id']).to be_present
|
|
||||||
expect(json_ft['financial_transaction_type_id']).to eq ftt1.id
|
|
||||||
expect(json_ft['financial_transaction_type_name']).to eq ftt1.name
|
|
||||||
expect(json_ft['amount']).to eq amount
|
|
||||||
expect(json_ft['note']).to eq note
|
|
||||||
expect(json_ft['user_id']).to eq user.id
|
|
||||||
end
|
|
||||||
|
|
||||||
it "updates the financial_transaction" do
|
|
||||||
resulting_ft = FinancialTransaction.where(id: json_ft['id']).first
|
|
||||||
expect(resulting_ft).to be_present
|
|
||||||
expect(resulting_ft.financial_transaction_type).to eq ftt1
|
|
||||||
expect(resulting_ft.amount).to eq amount
|
|
||||||
expect(resulting_ft.note).to eq note
|
|
||||||
expect(resulting_ft.user).to eq user
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples "financial_transactions endpoint failure" do |status|
|
|
||||||
it "returns status #{status}" do
|
|
||||||
request
|
|
||||||
expect(response.status).to eq status
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not change the ordergroup" do
|
|
||||||
expect { request }.to_not change {
|
|
||||||
user.ordergroup.attributes
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not change the financial_transactions of ordergroup" do
|
|
||||||
expect { request }.to_not change {
|
|
||||||
user.ordergroup.financial_transactions.count
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST :create" do
|
|
||||||
let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id, note: note } }
|
|
||||||
let(:request) { post :create, params: { financial_transaction: ft_params, foodcoop: 'f' } }
|
|
||||||
|
|
||||||
context 'without using self service' do
|
|
||||||
include_examples "financial_transactions endpoint failure", 403
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with using self service' do
|
|
||||||
before { FoodsoftConfig[:use_self_service] = true }
|
|
||||||
|
|
||||||
context "with no existing financial transaction" do
|
|
||||||
include_examples "financial_transactions create/update success"
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with existing financial transaction" do
|
|
||||||
before { user.ordergroup.add_financial_transaction! 5000, 'for ordering', user, ftt3 }
|
|
||||||
|
|
||||||
include_examples "financial_transactions create/update success"
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with invalid financial transaction type" do
|
|
||||||
let(:ft_params) { { amount: amount, financial_transaction_type_id: -1, note: note } }
|
|
||||||
|
|
||||||
include_examples "financial_transactions endpoint failure", 404
|
|
||||||
end
|
|
||||||
|
|
||||||
context "without note" do
|
|
||||||
let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id } }
|
|
||||||
|
|
||||||
include_examples "financial_transactions endpoint failure", 422
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without enough balance' do
|
|
||||||
before { FoodsoftConfig[:minimum_balance] = 1000 }
|
|
||||||
|
|
||||||
include_examples "financial_transactions endpoint failure", 403
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,220 +0,0 @@
|
||||||
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(:json_goa) { json_response['group_order_article'] }
|
|
||||||
let(:json_oa) { json_response['order_article'] }
|
|
||||||
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! }
|
|
||||||
|
|
||||||
shared_examples "group_order_articles endpoint success" do
|
|
||||||
before { request }
|
|
||||||
|
|
||||||
it "returns status 200" do
|
|
||||||
expect(response).to have_http_status :ok
|
|
||||||
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(:goa_params) { { quantity: new_quantity, tolerance: new_tolerance } }
|
|
||||||
let(:request) { patch :update, params: { id: goa.id, group_order_article: goa_params, foodcoop: 'f' } }
|
|
||||||
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! }
|
|
||||||
|
|
||||||
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(:request) { delete :destroy, params: { id: goa.id, foodcoop: 'f' } }
|
|
||||||
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! }
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,55 +0,0 @@
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
describe Api::V1::User::OrdergroupController, type: :controller do
|
|
||||||
include ApiOAuth
|
|
||||||
let(:user) { create :user, :ordergroup }
|
|
||||||
let(:api_scopes) { ['finance:user'] }
|
|
||||||
|
|
||||||
let(:ftc1) { create :financial_transaction_class }
|
|
||||||
let(:ftc2) { create :financial_transaction_class }
|
|
||||||
let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 }
|
|
||||||
let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 }
|
|
||||||
let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 }
|
|
||||||
|
|
||||||
describe "GET :financial_overview" do
|
|
||||||
let(:order) { create(:order, article_count: 1) }
|
|
||||||
let(:json_financial_overview) { json_response['financial_overview'] }
|
|
||||||
let(:oa_1) { order.order_articles.first }
|
|
||||||
|
|
||||||
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! }
|
|
||||||
|
|
||||||
before do
|
|
||||||
og = user.ordergroup
|
|
||||||
og.add_financial_transaction!(-1, '-1', user, ftt1)
|
|
||||||
og.add_financial_transaction!(2, '2', user, ftt1)
|
|
||||||
og.add_financial_transaction!(3, '3', user, ftt1)
|
|
||||||
|
|
||||||
og.add_financial_transaction!(-10, '-10', user, ftt2)
|
|
||||||
og.add_financial_transaction!(20, '20', user, ftt2)
|
|
||||||
og.add_financial_transaction!(30, '30', user, ftt2)
|
|
||||||
|
|
||||||
og.add_financial_transaction!(-100, '-100', user, ftt3)
|
|
||||||
og.add_financial_transaction!(200, '200', user, ftt3)
|
|
||||||
og.add_financial_transaction!(300, '300', user, ftt3)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns correct values" do
|
|
||||||
get :financial_overview, params: { foodcoop: 'f' }
|
|
||||||
expect(json_financial_overview['account_balance']).to eq 444
|
|
||||||
expect(json_financial_overview['available_funds']).to eq 444 - go.price
|
|
||||||
|
|
||||||
ftcs = Hash[json_financial_overview['financial_transaction_class_sums'].map { |x| [x['id'], x] }]
|
|
||||||
|
|
||||||
ftcs1 = ftcs[ftc1.id]
|
|
||||||
expect(ftcs1['name']).to eq ftc1.name
|
|
||||||
expect(ftcs1['amount']).to eq 4
|
|
||||||
|
|
||||||
ftcs2 = ftcs[ftc2.id]
|
|
||||||
expect(ftcs2['name']).to eq ftc2.name
|
|
||||||
expect(ftcs2['amount']).to eq 440
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
default: &defaults
|
default: &defaults
|
||||||
multi_coop_install: false
|
multi_coop_install: false
|
||||||
|
use_self_service: true
|
||||||
default_scope: 'f'
|
default_scope: 'f'
|
||||||
|
|
||||||
name: FC Minimal
|
name: FC Minimal
|
||||||
|
|
53
spec/requests/api/article_categories_spec.rb
Normal file
53
spec/requests/api/article_categories_spec.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Article Categories', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/article_categories' do
|
||||||
|
get 'article categories' do
|
||||||
|
tags 'Category'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
let(:order_article) { create(:order, article_count: 1).order_articles.first }
|
||||||
|
let(:stock_article) { create(:stock_article) }
|
||||||
|
let(:stock_order_article) { create(:stock_order, article_ids: [stock_article.id]).order_articles.first }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
article_categories: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/ArticleCategory'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/article_categories/{id}' do
|
||||||
|
get 'find article category by id' do
|
||||||
|
tags 'Category'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
response '200', 'article category found' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
article_categories: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/ArticleCategory'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { create(:article_category, name: 'dairy').id }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_cannot_find_object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
spec/requests/api/configs_spec.rb
Normal file
20
spec/requests/api/configs_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Config', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/config' do
|
||||||
|
get 'configuration variables' do
|
||||||
|
tags 'General'
|
||||||
|
produces 'application/json'
|
||||||
|
let(:api_scopes) { ['config:user'] }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {}
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
54
spec/requests/api/financial_transaction_classes_spec.rb
Normal file
54
spec/requests/api/financial_transaction_classes_spec.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Financial Transaction Classes', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/financial_transaction_classes' do
|
||||||
|
get 'financial transaction classes' do
|
||||||
|
tags 'Category'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
let(:financial_transaction_class) { create(:financial_transaction_class) }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref' => '#/components/schemas/Meta' },
|
||||||
|
financial_transaction_class: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransactionClass'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/financial_transaction_classes/{id}' do
|
||||||
|
get 'Retrieves a financial transaction class' do
|
||||||
|
tags 'Category'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
response '200', 'financial transaction class found' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
financial_transaction_classes: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransactionClass'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { create(:financial_transaction_class).id }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_cannot_find_object 'financial transaction class not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
52
spec/requests/api/financial_transaction_types_spec.rb
Normal file
52
spec/requests/api/financial_transaction_types_spec.rb
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Financial Transaction types', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/financial_transaction_types' do
|
||||||
|
get 'financial transaction types' do
|
||||||
|
tags 'Category'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
let(:financial_transaction_type) { create(:financial_transaction_type) }
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref' => '#/components/schemas/Meta' },
|
||||||
|
financial_transaction_type: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransactionType'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/financial_transaction_types/{id}' do
|
||||||
|
get 'find financial transaction type by id' do
|
||||||
|
tags 'Category'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
response '200', 'financial transaction type found' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
financial_transaction_types: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransactionType'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { create(:financial_transaction_type).id }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_cannot_find_object 'financial transaction type not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
spec/requests/api/financial_transactions_spec.rb
Normal file
56
spec/requests/api/financial_transactions_spec.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Financial Transaction', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
let!(:finance_user) { create(:user, groups: [create(:workgroup, role_finance: true)]) }
|
||||||
|
let!(:api_scopes) { ['finance:read', 'finance:write'] }
|
||||||
|
let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: finance_user.id, scopes: api_scopes&.join(' ')).token }
|
||||||
|
let(:financial_transaction) { create(:financial_transaction, user: user) }
|
||||||
|
|
||||||
|
path '/financial_transactions' do
|
||||||
|
get 'financial transactions' do
|
||||||
|
tags 'Financial Transaction'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref' => '#/components/schemas/Meta' },
|
||||||
|
financial_transaction: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransaction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/financial_transactions/{id}' do
|
||||||
|
get 'Retrieves a financial transaction ' do
|
||||||
|
tags 'Financial Transaction'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
response '200', 'financial transaction found' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
financial_transaction: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransaction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { FinancialTransaction.create(user: user).id }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_handles_invalid_scope_with_id
|
||||||
|
it_cannot_find_object 'financial transaction not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
24
spec/requests/api/navigations_spec.rb
Normal file
24
spec/requests/api/navigations_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Navigation', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/navigation' do
|
||||||
|
get 'navigation' do
|
||||||
|
tags 'General'
|
||||||
|
produces 'application/json'
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
navigation: {
|
||||||
|
'$ref' => '#/components/schemas/Navigation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
115
spec/requests/api/order_articles_spec.rb
Normal file
115
spec/requests/api/order_articles_spec.rb
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Order Articles', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/order_articles' do
|
||||||
|
get 'order articles' do
|
||||||
|
tags 'Order'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
q_ordered_url_param
|
||||||
|
|
||||||
|
let(:api_scopes) { ['orders:read', 'orders:write'] }
|
||||||
|
let(:order) { create(:order, article_count: 4) }
|
||||||
|
let(:order_articles) { order.order_articles }
|
||||||
|
|
||||||
|
before do
|
||||||
|
order_articles[0].update! quantity: 0, tolerance: 0, units_to_order: 0
|
||||||
|
order_articles[1].update! quantity: 1, tolerance: 0, units_to_order: 0
|
||||||
|
order_articles[2].update! quantity: 0, tolerance: 1, units_to_order: 0
|
||||||
|
order_articles[3].update! quantity: 0, tolerance: 0, units_to_order: 1
|
||||||
|
end
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref' => '#/components/schemas/Meta' },
|
||||||
|
order_articles: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/OrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
describe '(unset)' do
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'all' do
|
||||||
|
let(:q) { { q: { ordered: 'all' } } }
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
json_order_articles = JSON.parse(response.body)['order_articles']
|
||||||
|
json_order_article_ids = json_order_articles.map { |d| d['id'].to_i }
|
||||||
|
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when ordered by supplier' do
|
||||||
|
let(:q) { { q: { ordered: 'supplier' } } }
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
json_order_articles = JSON.parse(response.body)['order_articles']
|
||||||
|
json_order_article_ids = json_order_articles.map { |d| d['id'].to_i }
|
||||||
|
expect(json_order_article_ids).to match_array [order_articles[3].id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when ordered by member' do
|
||||||
|
let(:q) { { q: { ordered: 'member' } } }
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
json_order_articles = JSON.parse(response.body)['order_articles']
|
||||||
|
expect(json_order_articles.count).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ordered by user' do
|
||||||
|
let(:user) { create(:user, :ordergroup) }
|
||||||
|
let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:group_order_article, group_order: go, order_article: order_articles[1], quantity: 1)
|
||||||
|
create(:group_order_article, group_order: go, order_article: order_articles[2], tolerance: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'member' do
|
||||||
|
let(:q) { { q: { ordered: 'member' } } }
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
json_order_articles = JSON.parse(response.body)['order_articles']
|
||||||
|
json_order_article_ids = json_order_articles.map { |d| d['id'].to_i }
|
||||||
|
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/order_articles/{id}' do
|
||||||
|
get 'order articles' do
|
||||||
|
tags 'Order'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
let(:api_scopes) { ['orders:read', 'orders:write'] }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
order_article: {
|
||||||
|
'$ref': '#/components/schemas/OrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:order) { create(:order, article_count: 1) }
|
||||||
|
let(:id) { order.order_articles.first.id }
|
||||||
|
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
it_cannot_find_object 'order article not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
55
spec/requests/api/orders_spec.rb
Normal file
55
spec/requests/api/orders_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'Orders', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
let(:api_scopes) { ['orders:read'] }
|
||||||
|
|
||||||
|
path '/orders' do
|
||||||
|
get 'orders' do
|
||||||
|
tags 'Order'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
let(:order) { create(:order) }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref' => '#/components/schemas/Meta' },
|
||||||
|
ordes: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/Order'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/orders/{id}' do
|
||||||
|
get 'Order' do
|
||||||
|
tags 'Order'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
let(:order) { create(:order) }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
order: { '$ref' => '#/components/schemas/Order' }
|
||||||
|
}
|
||||||
|
let(:id) { order.id }
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
expect(JSON.parse(response.body)['order']['id']).to eq order.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
it_cannot_find_object 'order not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
106
spec/requests/api/user/financial_transactions_spec.rb
Normal file
106
spec/requests/api/user/financial_transactions_spec.rb
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'User', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
let(:api_scopes) { ['finance:user'] }
|
||||||
|
let(:user) { create :user, groups: [create(:ordergroup)] }
|
||||||
|
let(:other_user2) { create :user }
|
||||||
|
let(:ft) { create(:financial_transaction, user: user, ordergroup: user.ordergroup) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
ft
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/user/financial_transactions' do
|
||||||
|
post 'create new financial transaction (requires enabled self service)' do
|
||||||
|
tags 'Financial Transaction'
|
||||||
|
consumes 'application/json'
|
||||||
|
produces 'application/json'
|
||||||
|
|
||||||
|
parameter name: :financial_transaction, in: :body, schema: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
amount: { type: :integer },
|
||||||
|
financial_transaction_type: { type: :integer },
|
||||||
|
note: { type: :string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:financial_transaction) { { amount: 3, financial_transaction_type_id: create(:financial_transaction_type).id, note: 'lirum larum' } }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
financial_transaction: { '$ref': '#/components/schemas/FinancialTransaction' }
|
||||||
|
}
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_handles_invalid_scope_with_id 'user has no ordergroup, is below minimum balance, self service is disabled, or missing scope'
|
||||||
|
|
||||||
|
response '404', 'financial transaction type not found' do
|
||||||
|
schema '$ref' => '#/components/schemas/Error404'
|
||||||
|
let(:financial_transaction) { { amount: 3, financial_transaction_type_id: 'invalid', note: 'lirum larum' } }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response '422', 'invalid parameter value' do
|
||||||
|
xit 'TODO: fix controller to actually send a 422 for invalid params: https://github.com/foodcoops/foodsoft/issues/999'
|
||||||
|
# schema '$ref' => '#/components/schemas/Error422'
|
||||||
|
# let(:financial_transaction) { { amount: -3, financial_transaction_type_id: create(:financial_transaction_type).id, note: -2 } }
|
||||||
|
# run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "financial transactions of the member's ordergroup" do
|
||||||
|
tags 'User', 'Financial Transaction'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref': '#/components/schemas/Meta' },
|
||||||
|
financial_transaction: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransaction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data['financial_transactions'].first['id']).to eq(ft.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/user/financial_transactions/{id}' do
|
||||||
|
get 'find financial transaction by id' do
|
||||||
|
tags 'User', 'Financial Transaction'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
financial_transaction: {
|
||||||
|
'$ref': '#/components/schemas/FinancialTransaction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { ft.id }
|
||||||
|
run_test! do |response|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data['financial_transaction']['id']).to eq(ft.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_handles_invalid_scope_with_id 'user has no ordergroup or missing scope'
|
||||||
|
it_cannot_find_object 'financial transaction not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
192
spec/requests/api/user/group_order_articles_spec.rb
Normal file
192
spec/requests/api/user/group_order_articles_spec.rb
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'User', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
let(:api_scopes) { ['group_orders:user'] }
|
||||||
|
let(:user) { create :user, groups: [create(:ordergroup)] }
|
||||||
|
let(:other_user2) { create :user }
|
||||||
|
let(:order) { create(:order, article_count: 4) }
|
||||||
|
let(:order_articles) { order.order_articles }
|
||||||
|
let(:group_order) { create :group_order, ordergroup: user.ordergroup, order_id: order.id }
|
||||||
|
let(:goa) { create :group_order_article, group_order: group_order, order_article: order_articles.first }
|
||||||
|
|
||||||
|
before do
|
||||||
|
goa
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/user/group_order_articles' do
|
||||||
|
get 'group order articles' do
|
||||||
|
tags 'User', 'Order'
|
||||||
|
produces 'application/json'
|
||||||
|
pagination_param
|
||||||
|
q_ordered_url_param
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
meta: { '$ref': '#/components/schemas/Meta' },
|
||||||
|
group_order_article: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
'$ref': '#/components/schemas/GroupOrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data['group_order_articles'].first['id']).to eq(goa.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token
|
||||||
|
it_handles_invalid_scope 'user has no ordergroup or missing scope'
|
||||||
|
end
|
||||||
|
|
||||||
|
post 'create new group order article' do
|
||||||
|
tags 'User', 'Order'
|
||||||
|
consumes 'application/json'
|
||||||
|
produces 'application/json'
|
||||||
|
parameter name: :group_order_article, in: :body,
|
||||||
|
description: 'group order article to create',
|
||||||
|
required: true,
|
||||||
|
schema: { '$ref': '#/components/schemas/GroupOrderArticleForCreate' }
|
||||||
|
|
||||||
|
let(:group_order_article) { { order_article_id: order_articles.last.id, quantity: 1, tolerance: 2 } }
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
group_order_article: {
|
||||||
|
'$ref': '#/components/schemas/GroupOrderArticle'
|
||||||
|
},
|
||||||
|
order_article: {
|
||||||
|
'$ref': '#/components/schemas/OrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_handles_invalid_scope_with_id 'user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope'
|
||||||
|
|
||||||
|
response '404', 'order article not found in open orders' do
|
||||||
|
let(:group_order_article) { { order_article_id: 'invalid', quantity: 1, tolerance: 2 } }
|
||||||
|
schema '$ref' => '#/components/schemas/Error404'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response '422', 'invalid parameter value or group order article already exists' do
|
||||||
|
let(:group_order_article) { { order_article_id: goa.order_article_id, quantity: 1, tolerance: 2 } }
|
||||||
|
schema '$ref' => '#/components/schemas/Error422'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/user/group_order_articles/{id}' do
|
||||||
|
get 'find group order article by id' do
|
||||||
|
tags 'User', 'Order'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
group_order_article: {
|
||||||
|
'$ref': '#/components/schemas/GroupOrderArticle'
|
||||||
|
},
|
||||||
|
order_article: {
|
||||||
|
'$ref': '#/components/schemas/OrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:id) { goa.id }
|
||||||
|
run_test! do |response|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data['group_order_article']['id']).to eq(goa.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_scope_with_id
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
it_cannot_find_object 'group order article not found'
|
||||||
|
end
|
||||||
|
|
||||||
|
patch 'update a group order article (but delete if quantity and tolerance are zero)' do
|
||||||
|
tags 'User', 'Order'
|
||||||
|
consumes 'application/json'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
parameter name: :group_order_article, in: :body,
|
||||||
|
description: 'group order article update',
|
||||||
|
required: true,
|
||||||
|
schema: { '$ref': '#/components/schemas/GroupOrderArticleForUpdate' }
|
||||||
|
|
||||||
|
let(:id) { goa.id }
|
||||||
|
let(:group_order_article) { { order_article_id: goa.order_article_id, quantity: 2, tolerance: 2 } }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
group_order_article: {
|
||||||
|
'$ref': '#/components/schemas/GroupOrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response 401, 'not logged-in' do
|
||||||
|
schema '$ref' => '#/components/schemas/Error401'
|
||||||
|
let(:Authorization) { 'abc' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response 403, 'user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope' do
|
||||||
|
let(:api_scopes) { ['none'] }
|
||||||
|
schema '$ref' => '#/components/schemas/Error403'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response '404', 'order article not found in open orders' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
group_order_article: {
|
||||||
|
'$ref': '#/components/schemas/GroupOrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { 'invalid' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response '422', 'invalid parameter value' do
|
||||||
|
let(:group_order_article) { { order_article_id: 'invalid', quantity: -5, tolerance: 'invalid' } }
|
||||||
|
schema '$ref' => '#/components/schemas/Error422'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
delete 'remove group order article' do
|
||||||
|
tags 'User', 'Order'
|
||||||
|
consumes 'application/json'
|
||||||
|
produces 'application/json'
|
||||||
|
id_url_param
|
||||||
|
let(:api_scopes) { ['group_orders:user'] }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object, properties: {
|
||||||
|
group_order_article: {
|
||||||
|
'$ref': '#/components/schemas/GroupOrderArticle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:id) { goa.id }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_with_id
|
||||||
|
|
||||||
|
response 403, 'user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope' do
|
||||||
|
let(:api_scopes) { ['none'] }
|
||||||
|
schema '$ref' => '#/components/schemas/Error403'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_cannot_find_object 'order article not found in open orders'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
103
spec/requests/api/user/users_spec.rb
Normal file
103
spec/requests/api/user/users_spec.rb
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
describe 'User', type: :request do
|
||||||
|
include ApiHelper
|
||||||
|
|
||||||
|
path '/user' do
|
||||||
|
get 'info about the currently logged-in user' do
|
||||||
|
tags 'User'
|
||||||
|
produces 'application/json'
|
||||||
|
let(:api_scopes) { ['user:read'] }
|
||||||
|
let(:other_user1) { create :user }
|
||||||
|
let(:user) { create :user }
|
||||||
|
let(:other_user2) { create :user }
|
||||||
|
|
||||||
|
response '200', 'success' do
|
||||||
|
schema type: :object,
|
||||||
|
properties: {
|
||||||
|
user: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string,
|
||||||
|
description: 'full name'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: :string,
|
||||||
|
description: 'email address'
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: :string,
|
||||||
|
description: 'language code'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id name email]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test! do |response|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data['user']['id']).to eq(user.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/user/financial_overview' do
|
||||||
|
get 'financial summary about the currently logged-in user' do
|
||||||
|
tags 'User', 'Financial Transaction'
|
||||||
|
produces 'application/json'
|
||||||
|
let(:user) { create :user, :ordergroup }
|
||||||
|
let(:api_scopes) { ['finance:user'] }
|
||||||
|
FinancialTransactionClass.create(name: 'TestTransaction')
|
||||||
|
|
||||||
|
response 200, 'success' do
|
||||||
|
schema type: :object,
|
||||||
|
properties: {
|
||||||
|
financial_overview: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
|
||||||
|
account_balance: {
|
||||||
|
type: :number,
|
||||||
|
description: 'booked accout balance of ordergroup'
|
||||||
|
},
|
||||||
|
available_funds: {
|
||||||
|
type: :number,
|
||||||
|
description: 'fund available to order articles'
|
||||||
|
},
|
||||||
|
financial_transaction_class_sums: {
|
||||||
|
type: :array,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of the financial transaction class'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string,
|
||||||
|
description: 'name of the financial transaction class'
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
type: :number,
|
||||||
|
description: 'sum of the amounts belonging to the financial transaction class'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id name amount]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[account_balance available_funds financial_transaction_class_sums]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,21 +5,60 @@ module ApiHelper
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
let(:api_scopes) { [] } # empty scopes for stricter testing (in reality this would be default_scopes)
|
let(:api_scopes) { [] } # empty scopes for stricter testing (in reality this would be default_scopes)
|
||||||
let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token }
|
let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token }
|
||||||
let(:api_authorization) { "Bearer #{api_access_token}" }
|
let(:Authorization) { "Bearer #{api_access_token}" }
|
||||||
|
|
||||||
def self.it_handles_invalid_token(method, path, params_block = -> { api_auth })
|
def self.it_handles_invalid_token
|
||||||
context 'with invalid access token' do
|
context 'with invalid access token' do
|
||||||
let(:api_access_token) { 'abc' }
|
let(:Authorization) { 'abc' }
|
||||||
|
|
||||||
it { is_expected.to validate(method, path, 401, instance_exec(¶ms_block)) }
|
response 401, 'not logged-in' do
|
||||||
|
schema '$ref' => '#/components/schemas/Error401'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.it_handles_invalid_scope(method, path, params_block = -> { api_auth })
|
def self.it_handles_invalid_token_with_id
|
||||||
|
context 'with invalid access token' do
|
||||||
|
let(:Authorization) { 'abc' }
|
||||||
|
let(:id) { 42 } # id doesn't matter here
|
||||||
|
|
||||||
|
response 401, 'not logged-in' do
|
||||||
|
schema '$ref' => '#/components/schemas/Error401'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.it_handles_invalid_scope(description = 'missing scope')
|
||||||
context 'with invalid scope' do
|
context 'with invalid scope' do
|
||||||
let(:api_scopes) { ['none'] }
|
let(:api_scopes) { ['none'] }
|
||||||
|
|
||||||
it { is_expected.to validate(method, path, 403, instance_exec(¶ms_block)) }
|
response 403, description do
|
||||||
|
schema '$ref' => '#/components/schemas/Error403'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.it_handles_invalid_scope_with_id(description = 'missing scope')
|
||||||
|
context 'with invalid scope' do
|
||||||
|
let(:api_scopes) { ['none'] }
|
||||||
|
let(:id) { 42 } # id doesn't matter here
|
||||||
|
|
||||||
|
response 403, description do
|
||||||
|
schema '$ref' => '#/components/schemas/Error403'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.it_cannot_find_object(description = 'not found')
|
||||||
|
let(:id) { 'invalid' }
|
||||||
|
|
||||||
|
response 404, description do
|
||||||
|
schema '$ref' => '#/components/schemas/Error404'
|
||||||
|
run_test!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -27,13 +66,25 @@ module ApiHelper
|
||||||
it_handles_invalid_token(*args)
|
it_handles_invalid_token(*args)
|
||||||
it_handles_invalid_scope(*args)
|
it_handles_invalid_scope(*args)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Add authentication to parameters for {Swagger::RspecHelpers#validate}
|
def self.id_url_param
|
||||||
# @param params [Hash] Query parameters
|
parameter name: :id, in: :path, type: :integer, required: true
|
||||||
# @return Query parameters with authentication header
|
end
|
||||||
# @see Swagger::RspecHelpers#validate
|
|
||||||
def api_auth(params = {})
|
def self.pagination_param
|
||||||
{ '_headers' => { 'Authorization' => api_authorization } }.deep_merge(params)
|
parameter name: :per_page, in: :query, type: :integer, required: false
|
||||||
|
parameter name: :page, in: :query, type: :integer, required: false
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.q_ordered_url_param
|
||||||
|
parameter name: :q, in: :query, required: false,
|
||||||
|
description: "'member' show articles ordered by the user's ordergroup, 'all' by all members, and 'supplier' ordered at the supplier",
|
||||||
|
schema: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
ordered: { '$ref' => '#/components/schemas/q_ordered' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
513
spec/swagger_helper.rb
Normal file
513
spec/swagger_helper.rb
Normal file
|
@ -0,0 +1,513 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
# Specify a root folder where Swagger JSON files are generated
|
||||||
|
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
||||||
|
# to ensure that it's configured to serve Swagger from the same folder
|
||||||
|
config.swagger_root = Rails.root.join('swagger').to_s
|
||||||
|
|
||||||
|
# Define one or more Swagger documents and provide global metadata for each one
|
||||||
|
# When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will
|
||||||
|
# be generated at the provided relative path under swagger_root
|
||||||
|
# By default, the operations defined in spec files are added to the first
|
||||||
|
# document below. You can override this behavior by adding a swagger_doc tag to the
|
||||||
|
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
|
||||||
|
config.swagger_docs = {
|
||||||
|
'v1/swagger.yaml' => {
|
||||||
|
openapi: '3.0.3',
|
||||||
|
info: {
|
||||||
|
title: 'API V1',
|
||||||
|
version: 'v1'
|
||||||
|
},
|
||||||
|
paths: {},
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
|
pagination: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
recordCount: { type: :integer },
|
||||||
|
pageCount: { type: :integer },
|
||||||
|
currentPage: { type: :integer },
|
||||||
|
pageSize: { type: :integer }
|
||||||
|
},
|
||||||
|
required: %w(recordCount pageCount currentPage pageSize)
|
||||||
|
},
|
||||||
|
Order: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string,
|
||||||
|
description: "name of the order's supplier (or stock)"
|
||||||
|
},
|
||||||
|
starts: {
|
||||||
|
type: :string,
|
||||||
|
format: 'date-time',
|
||||||
|
description: 'when the order was opened'
|
||||||
|
},
|
||||||
|
ends: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
format: 'date-time',
|
||||||
|
description: 'when the order will close or was closed'
|
||||||
|
},
|
||||||
|
boxfill: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
format: 'date-time',
|
||||||
|
description: 'when the order will enter or entered the boxfill phase'
|
||||||
|
},
|
||||||
|
pickup: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
format: :date,
|
||||||
|
description: 'pickup date'
|
||||||
|
},
|
||||||
|
is_open: {
|
||||||
|
type: :boolean,
|
||||||
|
description: 'if the order is currently open or not'
|
||||||
|
},
|
||||||
|
is_boxfill: {
|
||||||
|
type: :boolean,
|
||||||
|
description: 'if the order is currently in the boxfill phase or not'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Article: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
supplier_id: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of supplier, or 0 for stock articles'
|
||||||
|
},
|
||||||
|
supplier_name: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'name of the supplier, or null for stock articles'
|
||||||
|
},
|
||||||
|
unit: {
|
||||||
|
type: :string,
|
||||||
|
description: 'amount of each unit, e.g. "100 g" or "kg"'
|
||||||
|
},
|
||||||
|
unit_quantity: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'units can only be ordered from the supplier in multiples of unit_quantity'
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'generic note'
|
||||||
|
},
|
||||||
|
manufacturer: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'manufacturer'
|
||||||
|
},
|
||||||
|
origin: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'origin, preferably (starting with a) 2-letter ISO country code'
|
||||||
|
},
|
||||||
|
article_category_id: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of article category'
|
||||||
|
},
|
||||||
|
quantity_available: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of units available (only present on stock articles)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id name supplier_id supplier_name unit unit_quantity note manufacturer origin article_category_id]
|
||||||
|
},
|
||||||
|
OrderArticle: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
order_id: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of order this order article belongs to'
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: :number,
|
||||||
|
format: :float,
|
||||||
|
description: 'foodcoop price'
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of units ordered by members'
|
||||||
|
},
|
||||||
|
tolerance: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of extra units that members are willing to buy to fill a box'
|
||||||
|
},
|
||||||
|
units_to_order: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of units to order from the supplier'
|
||||||
|
},
|
||||||
|
article: {
|
||||||
|
'$ref': '#/components/schemas/Article'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ArticleCategory: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id name]
|
||||||
|
},
|
||||||
|
FinancialTransaction: {
|
||||||
|
allOf: [
|
||||||
|
{ '$ref': '#/components/schemas/FinancialTransactionForCreate' },
|
||||||
|
{
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
type: :number,
|
||||||
|
format: :float,
|
||||||
|
description: 'amount credited (negative for a debit transaction)'
|
||||||
|
},
|
||||||
|
financial_transaction_type_id: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of the type of the transaction'
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
type: :string,
|
||||||
|
description: 'note entered with the transaction'
|
||||||
|
},
|
||||||
|
user_id: {
|
||||||
|
type: :integer,
|
||||||
|
nullable: true,
|
||||||
|
description: 'id of user who entered the transaction (may be <tt>null</tt> for deleted users or 0 for a system user)'
|
||||||
|
},
|
||||||
|
user_name: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'name of user who entered the transaction (may be <tt>null</tt> or empty string for deleted users or system users)'
|
||||||
|
},
|
||||||
|
financial_transaction_type_name: {
|
||||||
|
type: :string,
|
||||||
|
description: 'name of the type of the transaction'
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
type: :string,
|
||||||
|
format: 'date-time',
|
||||||
|
description: 'when the transaction was entered'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id user_id user_name financial_transaction_type_name created_at]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FinancialTransactionForCreate: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
amount: {
|
||||||
|
type: :number,
|
||||||
|
format: :float,
|
||||||
|
description: 'amount credited (negative for a debit transaction)'
|
||||||
|
},
|
||||||
|
financial_transaction_type_id:
|
||||||
|
{
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of the type of the transaction'
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
type: :string,
|
||||||
|
description: 'note entered with the transaction'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[amount note user_id]
|
||||||
|
},
|
||||||
|
FinancialTransactionClass: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id name]
|
||||||
|
},
|
||||||
|
FinancialTransactionType: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
name_short: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'short name (used for bank transfers)'
|
||||||
|
},
|
||||||
|
bank_account_id: {
|
||||||
|
type: :integer,
|
||||||
|
nullable: true,
|
||||||
|
description: 'id of the bank account used for this transaction type'
|
||||||
|
},
|
||||||
|
bank_account_name: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'name of the bank account used for this transaction type'
|
||||||
|
},
|
||||||
|
bank_account_iban: {
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: 'IBAN of the bank account used for this transaction type'
|
||||||
|
},
|
||||||
|
financial_transaction_class_id: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of the class of the transaction'
|
||||||
|
},
|
||||||
|
financial_transaction_class_name: {
|
||||||
|
type: :string,
|
||||||
|
description: 'name of the class of the transaction'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[id name financial_transaction_class_id financial_transaction_class_name]
|
||||||
|
},
|
||||||
|
GroupOrderArticleForUpdate: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
quantity:
|
||||||
|
{
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of units ordered by the users ordergroup'
|
||||||
|
},
|
||||||
|
tolerance:
|
||||||
|
{
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of extra units the users ordergroup is willing to buy for filling a box'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GroupOrderArticleForCreate: {
|
||||||
|
allOf: [
|
||||||
|
{ '$ref': '#/components/schemas/GroupOrderArticleForUpdate' },
|
||||||
|
{
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
order_article_id:
|
||||||
|
{
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of order article'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
GroupOrderArticle: {
|
||||||
|
allOf: [
|
||||||
|
{ '$ref': '#/components/schemas/GroupOrderArticleForCreate' },
|
||||||
|
{
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
type: :number,
|
||||||
|
format: :float,
|
||||||
|
description: 'number of units the users ordergroup will receive or has received'
|
||||||
|
},
|
||||||
|
total_price:
|
||||||
|
{
|
||||||
|
type: :number,
|
||||||
|
format: :float,
|
||||||
|
description: 'total price of this group order article'
|
||||||
|
},
|
||||||
|
order_article_id:
|
||||||
|
{
|
||||||
|
type: :integer,
|
||||||
|
description: 'id of order article'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[order_article_id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
q_ordered: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
ordered: {
|
||||||
|
type: :string,
|
||||||
|
enum: %w[member all supplier]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Meta: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
page: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'page number of the returned collection'
|
||||||
|
},
|
||||||
|
per_page: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'number of items per page'
|
||||||
|
},
|
||||||
|
total_pages: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'total number of pages'
|
||||||
|
},
|
||||||
|
total_count: {
|
||||||
|
type: :integer,
|
||||||
|
description: 'total number of items in the collection'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[page per_page total_pages total_count]
|
||||||
|
},
|
||||||
|
Navigation: {
|
||||||
|
type: :array,
|
||||||
|
items: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: :string,
|
||||||
|
description: 'title'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: :string,
|
||||||
|
description: 'link'
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
'$ref': "#/components/schemas/Navigation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
minProperties: 2 # name+url or name+items
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
error: {
|
||||||
|
type: :string,
|
||||||
|
description: 'error code'
|
||||||
|
},
|
||||||
|
error_description: {
|
||||||
|
type: :string,
|
||||||
|
description: 'human-readable error message (localized)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error401: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
error: {
|
||||||
|
type: :string,
|
||||||
|
description: '<tt>unauthorized</tt>'
|
||||||
|
},
|
||||||
|
error_description: {
|
||||||
|
'$ref': '#/components/schemas/Error/properties/error_description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error403: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
error: {
|
||||||
|
type: :string,
|
||||||
|
description: '<tt>forbidden</tt> or <tt>invalid_scope</tt>'
|
||||||
|
},
|
||||||
|
error_description: {
|
||||||
|
'$ref': '#/components/schemas/Error/properties/error_description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error404: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
error: {
|
||||||
|
type: :string,
|
||||||
|
description: '<tt>not_found</tt>'
|
||||||
|
},
|
||||||
|
error_description: {
|
||||||
|
'$ref': '#/components/schemas/Error/properties/error_description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error422: {
|
||||||
|
type: :object,
|
||||||
|
properties: {
|
||||||
|
error: {
|
||||||
|
type: :string,
|
||||||
|
description: '<tt>unprocessable entity</tt>'
|
||||||
|
},
|
||||||
|
error_description: {
|
||||||
|
'$ref': '#/components/schemas/Error/properties/error_description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
securitySchemes: {
|
||||||
|
oauth2: {
|
||||||
|
type: :oauth2,
|
||||||
|
flows: {
|
||||||
|
implicit: {
|
||||||
|
authorizationUrl: 'http://localhost:3000/f/oauth/authorize',
|
||||||
|
scopes: {
|
||||||
|
'config:user': 'reading Foodsoft configuration for regular users',
|
||||||
|
'config:read': 'reading Foodsoft configuration values',
|
||||||
|
'config:write': 'reading and updating Foodsoft configuration values',
|
||||||
|
'finance:user': 'accessing your own financial transactions',
|
||||||
|
'finance:read': 'reading all financial transactions',
|
||||||
|
'finance:write': 'reading and creating financial transactions',
|
||||||
|
'user:read': 'reading your own user profile',
|
||||||
|
'user:write': 'reading and updating your own user profile',
|
||||||
|
offline_access: 'retain access after user has logged out'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: 'http://{defaultHost}/f/api/v1',
|
||||||
|
variables: {
|
||||||
|
defaultHost: {
|
||||||
|
default: 'localhost:3000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
security: [
|
||||||
|
oauth2: [
|
||||||
|
'user:read'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'.
|
||||||
|
# The swagger_docs configuration option has the filename including format in
|
||||||
|
# the key, this may want to be changed to avoid putting yaml in json files.
|
||||||
|
# Defaults to json. Accepts ':json' and ':yaml'.
|
||||||
|
config.swagger_format = :yaml
|
||||||
|
end
|
Loading…
Reference in a new issue