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:
Philipp Rothmann 2023-05-12 11:11:48 +02:00 committed by GitHub
parent 8604e27fe9
commit c67e9b5be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1478 additions and 1858 deletions

View File

@ -451,6 +451,15 @@ RSpec/DescribedClass:
- "spec/models/ordergroup_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
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
@ -581,6 +590,14 @@ RSpec/ScatteredSetup:
- "spec/integration/balancing_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
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:

View File

@ -55,6 +55,9 @@ gem 'gaffe'
gem 'ruby-filemagic'
gem 'mime-types'
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
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-lcov', require: false
# api
gem 'apivore', require: false
gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114
gem 'rswag-specs'
end

View File

@ -109,13 +109,6 @@ GEM
activerecord (>= 3.0.0)
addressable (2.8.1)
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)
capybara (~> 3.13, < 4)
websocket-driver (>= 0.6.5)
@ -430,6 +423,16 @@ GEM
rspec-rerun (1.1.0)
rspec (~> 3.0)
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)
json (~> 2.3)
parallel (~> 1.10)
@ -557,7 +560,6 @@ DEPENDENCIES
active_model_serializers (~> 0.10.0)
acts_as_tree
acts_as_versioned!
apivore
apparition
attribute_normalizer
better_errors
@ -617,6 +619,9 @@ DEPENDENCIES
rspec-core
rspec-rails
rspec-rerun
rswag-api
rswag-specs
rswag-ui
rubocop
rubocop-rails
rubocop-rspec

View 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

View 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

View File

@ -1,4 +1,7 @@
# rubocop:disable Metrics/BlockLength
Rails.application.routes.draw do
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
get "order_comments/new"
get "comments/new"
@ -290,3 +293,4 @@ Rails.application.routes.draw do
resources :users, only: [:index]
end # End of /:foodcoop scope
end
# rubocop:enable Metrics/BlockLength

View File

@ -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
we're open for new additions.
The API is documented using [Open API 2.0](https://github.com/OAI/OpenAPI-Specification)
/ [Swagger](https://swagger.io/) in [swagger.v1.yml](swagger.v1.yml).
The API is documented using [Open API 3.0.1](https://github.com/OAI/OpenAPI-Specification)
/ [Swagger](https://swagger.io/) in [swagger.yaml](/swagger/v1/swagger.yaml).
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.

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -6,6 +6,7 @@
default: &defaults
multi_coop_install: false
use_self_service: true
default_scope: 'f'
name: FC Minimal

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
</