diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index b7e21eab..2303bab6 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -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:
diff --git a/Gemfile b/Gemfile
index a6e27fae..61562099 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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
diff --git a/Gemfile.lock b/Gemfile.lock
index 196f9dbd..ebc7a49f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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
diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb
new file mode 100644
index 00000000..e4b798f6
--- /dev/null
+++ b/config/initializers/rswag_api.rb
@@ -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
diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb
new file mode 100644
index 00000000..cc9f2ef8
--- /dev/null
+++ b/config/initializers/rswag_ui.rb
@@ -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
diff --git a/config/routes.rb b/config/routes.rb
index 5b27eba4..764dcab0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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
diff --git a/doc/API.md b/doc/API.md
index 2e09cfa4..f295e82f 100644
--- a/doc/API.md
+++ b/doc/API.md
@@ -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.
diff --git a/doc/swagger.v1.yml b/doc/swagger.v1.yml
deleted file mode 100644
index d8e793d3..00000000
--- a/doc/swagger.v1.yml
+++ /dev/null
@@ -1,1106 +0,0 @@
-swagger: '2.0'
-info:
- title: Foodsoft API v1
- version: '1.0.0'
- description: >
- [Foodsoft](https://github.com/foodcoops/foodsoft) is web-based software to manage
- a non-profit food coop (product catalog, ordering, accounting, job scheduling).
-
-
- This is a description of Foodsoft's API v1.
-
-
- Note that each food cooperative typically has their own instance (on a shared
- server or their own installation), and there are just as many APIs (if the Foodsoft
- version is recent enough).
- This API description points to the default development url with the default
- Foodsoft scope - that would be [http://localhost:3000/f](http://localhost:3000/f).
-
- You may find the search parameters for index endpoints lacking. They are not
- documented here, because there are too many combinations. For now, you'll need
- to resort to [Ransack](https://github.com/activerecord-hackery/ransack) and
- looking at Foodsoft's `ransackable_*` model class methods.
-externalDocs:
- description: General Foodsoft API documentation
- url: https://github.com/foodcoops/foodsoft/blob/master/doc/API.md
-
-# development url with default scope
-host: localhost:3000
-schemes:
- - 'http'
-basePath: /f/api/v1
-
-produces:
- - 'application/json'
-
-paths:
- /user:
- get:
- summary: info about the currently logged-in user
- tags:
- - 1. User
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- user:
- $ref: '#/definitions/User'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['user:read', 'user:write']
-
- /user/financial_overview:
- get:
- summary: financial summary about the currently logged-in user
- tags:
- - 1. User
- - 6. FinancialTransaction
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_overview:
- $ref: '#/definitions/FinanceOverview'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['finance:user']
-
- /user/financial_transactions:
- get:
- summary: financial transactions of the member's ordergroup
- tags:
- - 1. User
- - 6. FinancialTransaction
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transactions:
- type: array
- items:
- $ref: '#/definitions/FinancialTransaction'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup or missing scope
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['finance:user']
- post:
- summary: create new financial transaction (requires enabled self service)
- tags:
- - 1. User
- - 6. FinancialTransaction
- parameters:
- - in: body
- name: body
- description: financial transaction to create
- required: true
- schema:
- $ref: '#/definitions/FinancialTransactionForCreate'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction:
- $ref: '#/definitions/FinancialTransaction'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup, is below minimum balance, self service is disabled, or missing scope
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: financial transaction type not found
- schema:
- $ref: '#/definitions/Error404'
- 422:
- description: invalid parameter value
- schema:
- $ref: '#/definitions/Error422'
- /user/financial_transactions/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find financial transaction by id
- tags:
- - 1. User
- - 6. FinancialTransaction
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction:
- $ref: '#/definitions/FinancialTransaction'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup or missing scope
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['finance:user']
-
- /user/group_order_articles:
- get:
- summary: group order articles
- tags:
- - 1. User
- - 2. Order
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- - $ref: '#/parameters/q_ordered'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- group_order_articles:
- type: array
- items:
- $ref: '#/definitions/GroupOrderArticle'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup or missing scope
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['group_orders:user']
- post:
- summary: create new group order article
- tags:
- - 1. User
- - 2. Order
- parameters:
- - in: body
- name: body
- description: group order article to create
- required: true
- schema:
- $ref: '#/definitions/GroupOrderArticleForCreate'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- group_order_article:
- $ref: '#/definitions/GroupOrderArticle'
- order_article:
- $ref: '#/definitions/OrderArticle'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: order article not found in open orders
- schema:
- $ref: '#/definitions/Error404'
- 422:
- description: invalid parameter value or group order article already exists
- schema:
- $ref: '#/definitions/Error422'
- /user/group_order_articles/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find group order article by id
- tags:
- - 1. User
- - 2. Order
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- group_order_article:
- $ref: '#/definitions/GroupOrderArticle'
- order_article:
- $ref: '#/definitions/OrderArticle'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup or missing scope
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['group_orders:user']
- patch:
- summary: update a group order article (but delete if quantity and tolerance are zero)
- tags:
- - 1. User
- - 2. Order
- parameters:
- - $ref: '#/parameters/idInUrl'
- - in: body
- name: body
- description: group order article update
- required: true
- schema:
- $ref: '#/definitions/GroupOrderArticleForUpdate'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- group_order_article:
- $ref: '#/definitions/GroupOrderArticle'
- order_article:
- $ref: '#/definitions/OrderArticle'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: order article not found in open orders
- schema:
- $ref: '#/definitions/Error404'
- 422:
- description: invalid parameter value
- schema:
- $ref: '#/definitions/Error422'
- delete:
- summary: remove group order article
- tags:
- - 1. User
- - 2. Order
- parameters:
- - $ref: '#/parameters/idInUrl'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- group_order_article:
- $ref: '#/definitions/GroupOrderArticle'
- order_article:
- $ref: '#/definitions/OrderArticle'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: order article not found in open orders
- schema:
- $ref: '#/definitions/Error404'
-
- /financial_transactions:
- get:
- summary: financial transactions
- tags:
- - 6. FinancialTransaction
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transactions:
- type: array
- items:
- $ref: '#/definitions/FinancialTransaction'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['finance:read', 'finance:write']
- /financial_transactions/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find financial transaction by id
- tags:
- - 6. FinancialTransaction
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction:
- $ref: '#/definitions/FinancialTransaction'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['finance:read', 'finance:write']
- /orders:
- get:
- summary: orders
- tags:
- - 2. Order
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- orders:
- type: array
- items:
- $ref: '#/definitions/Order'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['orders:read', 'orders:write']
- /orders/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find order by id
- tags:
- - 2. Order
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- order:
- $ref: '#/definitions/Order'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['orders:read', 'orders:write']
- /order_articles:
- get:
- summary: order articles
- tags:
- - 2. Order
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- - $ref: '#/parameters/q_ordered'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- order_articles:
- type: array
- items:
- $ref: '#/definitions/OrderArticle'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['group_orders:user']
- /order_articles/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find order article by id
- tags:
- - 2. Order
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- order_article:
- $ref: '#/definitions/OrderArticle'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['orders:read', 'orders:write']
- /article_categories:
- get:
- summary: article categories
- tags:
- - 2. Category
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- article_categories:
- type: array
- items:
- $ref: '#/definitions/ArticleCategory'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
-
- security:
- - foodsoft_auth: ['all']
- /article_categories/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find article category by id
- tags:
- - 2. Category
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- article_category:
- $ref: '#/definitions/ArticleCategory'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['all']
-
- /financial_transaction_classes:
- get:
- summary: financial transaction classes
- tags:
- - 2. Category
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction_classes:
- type: array
- items:
- $ref: '#/definitions/FinancialTransactionClass'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
-
- security:
- - foodsoft_auth: ['all']
- /financial_transaction_classes/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find financial transaction class by id
- tags:
- - 2. Category
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction_class:
- $ref: '#/definitions/FinancialTransactionClass'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['all']
-
- /financial_transaction_types:
- get:
- summary: financial transaction types
- tags:
- - 2. Category
- parameters:
- - $ref: '#/parameters/page'
- - $ref: '#/parameters/per_page'
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction_types:
- type: array
- items:
- $ref: '#/definitions/FinancialTransactionType'
- meta:
- $ref: '#/definitions/Meta'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
-
- security:
- - foodsoft_auth: ['all']
- /financial_transaction_types/{id}:
- parameters:
- - $ref: '#/parameters/idInUrl'
- get:
- summary: find financial transaction type by id
- tags:
- - 2. Category
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- financial_transaction_type:
- $ref: '#/definitions/FinancialTransactionType'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 404:
- description: not found
- schema:
- $ref: '#/definitions/Error404'
- security:
- - foodsoft_auth: ['all']
-
- /config:
- get:
- summary: configuration variables
- tags:
- - 7. General
- responses:
- 200:
- description: success
- schema:
- type: object
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- 403:
- description: missing scope or no permission
- schema:
- $ref: '#/definitions/Error403'
- security:
- - foodsoft_auth: ['config:user', 'config:read', 'config:write']
- /navigation:
- get:
- summary: navigation
- tags:
- - 7. General
- responses:
- 200:
- description: success
- schema:
- type: object
- properties:
- navigation:
- $ref: '#/definitions/Navigation'
- 401:
- description: not logged-in
- schema:
- $ref: '#/definitions/Error401'
- security:
- - foodsoft_auth: []
-
-parameters:
- # url parameters
- idInUrl:
- name: id
- type: integer
- in: path
- minimum: 1
- required: true
-
- # query parameters
- page:
- name: page
- type: integer
- in: query
- description: page number
- minimum: 0
- default: 0
- per_page:
- name: per_page
- type: integer
- in: query
- description: items per page
- minimum: 0
- default: 20
-
- # non-ransack query parameters
- q_ordered:
- name: q[ordered]
- type: string
- in: query
- description: "'member' show articles ordered by the user's ordergroup, 'all' by all members, and 'supplier' ordered at the supplier"
- enum: ['member', 'all', 'supplier']
-
-definitions:
- # models
- 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: ['id', 'name', 'email']
-
- FinancialTransactionForCreate:
- type: object
- properties:
- amount:
- type: number
- 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: ['amount', 'financial_transaction_type_id', 'note']
- FinancialTransaction:
- allOf:
- - $ref: '#/definitions/FinancialTransactionForCreate'
- - type: object
- properties:
- id:
- type: integer
- user_id:
- type: ['integer', 'null']
- description: id of user who entered the transaction (may be null for deleted users or 0 for a system user)
- user_name:
- type: ['string', 'null']
- description: name of user who entered the transaction (may be null 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: ['id', 'user_id', 'user_name', 'financial_transaction_type_name', 'created_at']
-
- FinancialTransactionClass:
- type: object
- properties:
- id:
- type: integer
- name:
- type: string
- description: full name
- required: ['id', 'name']
-
- FinancialTransactionType:
- type: object
- properties:
- id:
- type: integer
- name:
- type: string
- description: full name
- name_short:
- type: ['string', 'null']
- description: short name (used for bank transfers)
- bank_account_id:
- type: ['integer', 'null']
- description: id of the bank account used for this transaction type
- bank_account_name:
- type: ['string', 'null']
- description: name of the bank account used for this transaction type
- bank_account_iban:
- type: ['string', 'null']
- 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: ['id', 'name', 'financial_transaction_class_id', 'financial_transaction_class_name']
-
- FinanceOverview:
- 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
- items:
- type: object
- 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: ['id', 'name', 'amount']
- required: ['account_balance', 'available_funds', 'financial_transaction_class_sums']
-
- ArticleCategory:
- type: object
- properties:
- id:
- type: integer
- name:
- type: string
- required: ['id', 'name']
-
- 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', 'null']
- format: date-time
- description: when the order will close or was closed
- boxfill:
- type: ['string', 'null']
- format: date-time
- description: when the order will enter or entered the boxfill phase
- pickup:
- type: ['string', 'null']
- 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', 'null']
- 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', 'null']
- description: generic note
- manufacturer:
- type: ['string', 'null']
- description: manufacturer
- origin:
- type: ['string', 'null']
- 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: ['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: '#/definitions/Article'
-
- GroupOrderArticleForUpdate:
- type: object
- properties:
- quantity:
- type: integer
- description: number of units ordered by the user's ordergroup
- tolerance:
- type: integer
- description: number of extra units the user's ordergroup is willing to buy for filling a box
- GroupOrderArticleForCreate:
- allOf:
- - $ref: '#/definitions/GroupOrderArticleForUpdate'
- - type: object
- properties:
- order_article_id:
- type: integer
- description: id of order article
- GroupOrderArticle:
- allOf:
- - $ref: '#/definitions/GroupOrderArticleForCreate'
- - type: object
- properties:
- id:
- type: integer
- result:
- type: number
- format: float
- description: number of units the user's ordergroup will receive or has received
- total_price:
- type: number
- format: float
- description: total price of this group order article
-
- Navigation:
- type: array
- items:
- type: object
- properties:
- name:
- type: string
- description: title
- url:
- type: string
- description: link
- items:
- $ref: '#/definitions/Navigation'
- required: ['name']
- minProperties: 2 # name+url or name+items
-
- # collection meta object in root of a response
- 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: ['page', 'per_page', 'total_pages', 'total_count']
-
- Error:
- type: object
- properties:
- error:
- type: string
- description: error code
- error_description:
- type: string
- description: human-readable error message (localized)
- Error404:
- type: object
- properties:
- error:
- type: string
- description: 'not_found'
- error_description:
- $ref: '#/definitions/Error/properties/error_description'
- Error401:
- type: object
- properties:
- error:
- type: string
- description: 'unauthorized'
- error_description:
- $ref: '#/definitions/Error/properties/error_description'
- Error403:
- type: object
- properties:
- error:
- type: string
- description: 'forbidden or invalid_scope'
- error_description:
- $ref: '#/definitions/Error/properties/error_description'
- Error422:
- type: object
- properties:
- error:
- type: string
- description: unprocessable entity
- error_description:
- $ref: '#/definitions/Error/properties/error_description'
-
-
-securityDefinitions:
- foodsoft_auth:
- type: oauth2
- flow: 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
diff --git a/spec/api/v1/order_articles_spec.rb b/spec/api/v1/order_articles_spec.rb
deleted file mode 100644
index e65867db..00000000
--- a/spec/api/v1/order_articles_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/api/v1/swagger_spec.rb b/spec/api/v1/swagger_spec.rb
deleted file mode 100644
index 3da37332..00000000
--- a/spec/api/v1/swagger_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/api/v1/user/financial_transactions_spec.rb b/spec/api/v1/user/financial_transactions_spec.rb
deleted file mode 100644
index c7e8f826..00000000
--- a/spec/api/v1/user/financial_transactions_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/api/v1/user/group_order_articles_spec.rb b/spec/api/v1/user/group_order_articles_spec.rb
deleted file mode 100644
index 3bfa299e..00000000
--- a/spec/api/v1/user/group_order_articles_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/api/v1/user/ordergroup_spec.rb b/spec/api/v1/user/ordergroup_spec.rb
deleted file mode 100644
index 5eacb63e..00000000
--- a/spec/api/v1/user/ordergroup_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/app_config.yml b/spec/app_config.yml
index 2e146be9..a9bd72b0 100644
--- a/spec/app_config.yml
+++ b/spec/app_config.yml
@@ -6,6 +6,7 @@
default: &defaults
multi_coop_install: false
+ use_self_service: true
default_scope: 'f'
name: FC Minimal
diff --git a/spec/requests/api/article_categories_spec.rb b/spec/requests/api/article_categories_spec.rb
new file mode 100644
index 00000000..4c079ff2
--- /dev/null
+++ b/spec/requests/api/article_categories_spec.rb
@@ -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
diff --git a/spec/requests/api/configs_spec.rb b/spec/requests/api/configs_spec.rb
new file mode 100644
index 00000000..75f48ceb
--- /dev/null
+++ b/spec/requests/api/configs_spec.rb
@@ -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
diff --git a/spec/requests/api/financial_transaction_classes_spec.rb b/spec/requests/api/financial_transaction_classes_spec.rb
new file mode 100644
index 00000000..1eaf046f
--- /dev/null
+++ b/spec/requests/api/financial_transaction_classes_spec.rb
@@ -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
diff --git a/spec/requests/api/financial_transaction_types_spec.rb b/spec/requests/api/financial_transaction_types_spec.rb
new file mode 100644
index 00000000..82a30f83
--- /dev/null
+++ b/spec/requests/api/financial_transaction_types_spec.rb
@@ -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
diff --git a/spec/requests/api/financial_transactions_spec.rb b/spec/requests/api/financial_transactions_spec.rb
new file mode 100644
index 00000000..1d3ef2b9
--- /dev/null
+++ b/spec/requests/api/financial_transactions_spec.rb
@@ -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
diff --git a/spec/requests/api/navigations_spec.rb b/spec/requests/api/navigations_spec.rb
new file mode 100644
index 00000000..c2312437
--- /dev/null
+++ b/spec/requests/api/navigations_spec.rb
@@ -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
diff --git a/spec/requests/api/order_articles_spec.rb b/spec/requests/api/order_articles_spec.rb
new file mode 100644
index 00000000..17feefa6
--- /dev/null
+++ b/spec/requests/api/order_articles_spec.rb
@@ -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
diff --git a/spec/requests/api/orders_spec.rb b/spec/requests/api/orders_spec.rb
new file mode 100644
index 00000000..c0505d7f
--- /dev/null
+++ b/spec/requests/api/orders_spec.rb
@@ -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
diff --git a/spec/requests/api/user/financial_transactions_spec.rb b/spec/requests/api/user/financial_transactions_spec.rb
new file mode 100644
index 00000000..aca9d7cd
--- /dev/null
+++ b/spec/requests/api/user/financial_transactions_spec.rb
@@ -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
diff --git a/spec/requests/api/user/group_order_articles_spec.rb b/spec/requests/api/user/group_order_articles_spec.rb
new file mode 100644
index 00000000..205a4070
--- /dev/null
+++ b/spec/requests/api/user/group_order_articles_spec.rb
@@ -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
diff --git a/spec/requests/api/user/users_spec.rb b/spec/requests/api/user/users_spec.rb
new file mode 100644
index 00000000..0d3196bc
--- /dev/null
+++ b/spec/requests/api/user/users_spec.rb
@@ -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
diff --git a/spec/support/api_helper.rb b/spec/support/api_helper.rb
index ee0225f5..86e2ca07 100644
--- a/spec/support/api_helper.rb
+++ b/spec/support/api_helper.rb
@@ -5,21 +5,60 @@ module ApiHelper
let(:user) { create(:user) }
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_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
- 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
- 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
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
@@ -27,13 +66,25 @@ module ApiHelper
it_handles_invalid_token(*args)
it_handles_invalid_scope(*args)
end
- end
- # Add authentication to parameters for {Swagger::RspecHelpers#validate}
- # @param params [Hash] Query parameters
- # @return Query parameters with authentication header
- # @see Swagger::RspecHelpers#validate
- def api_auth(params = {})
- { '_headers' => { 'Authorization' => api_authorization } }.deep_merge(params)
+ def self.id_url_param
+ parameter name: :id, in: :path, type: :integer, required: true
+ end
+
+ def self.pagination_param
+ 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
diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb
new file mode 100644
index 00000000..912504b8
--- /dev/null
+++ b/spec/swagger_helper.rb
@@ -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 null 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 null 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: 'unauthorized'
+ },
+ error_description: {
+ '$ref': '#/components/schemas/Error/properties/error_description'
+ }
+ }
+ },
+ Error403: {
+ type: :object,
+ properties: {
+ error: {
+ type: :string,
+ description: 'forbidden or invalid_scope'
+ },
+ error_description: {
+ '$ref': '#/components/schemas/Error/properties/error_description'
+ }
+ }
+ },
+ Error404: {
+ type: :object,
+ properties: {
+ error: {
+ type: :string,
+ description: 'not_found'
+ },
+ error_description: {
+ '$ref': '#/components/schemas/Error/properties/error_description'
+ }
+ }
+ },
+ Error422: {
+ type: :object,
+ properties: {
+ error: {
+ type: :string,
+ description: 'unprocessable entity'
+ },
+ 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