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/spec/requests/api/user_spec.rb b/spec/requests/api/user_spec.rb new file mode 100644 index 00000000..ed002ba1 --- /dev/null +++ b/spec/requests/api/user_spec.rb @@ -0,0 +1,96 @@ +require 'swagger_helper' + +describe 'User API', 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', 'FinancialTransaction' + produces 'application/json' + let!(:user) { create :user, :ordergroup } + + response 200, 'success' do + schema 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: :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: %w[id name amount] + }, + required: %w[account_balance available_funds financial_transaction_class_sums] + } + } + + let(:api_scopes) { ['finance:user'] } + run_test! + end + + it_handles_invalid_token_and_scope + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb deleted file mode 100644 index d6542fc3..00000000 --- a/spec/requests/api/users_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'swagger_helper' - -describe 'Users API', 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_user_1) { create :user } - let(:user) { create :user } - let(:other_user_2) { create :user } - - response '200', 'success' do - 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', 'FinancialTransaction' - let!(:user) { create :user, :ordergroup } - - response 200, 'success' do - let(:api_scopes) { ['finance:user'] } - 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 504c59c8..c5357e7d 100644 --- a/spec/support/api_helper.rb +++ b/spec/support/api_helper.rb @@ -7,12 +7,12 @@ module ApiHelper let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token } let(:Authorization) { "Bearer #{api_access_token}" } - # TODO: not needed anymore? def self.it_handles_invalid_token() context 'with invalid access token' do let(:Authorization) { 'abc' } response 401, 'not logged-in' do + schema '$ref' => '#/components/schemas/Error401' run_test! end end @@ -23,6 +23,7 @@ module ApiHelper let(:api_scopes) { ['none'] } response 403, 'missing scope' do + schema '$ref' => '#/components/schemas/Error403' run_test! end end @@ -33,13 +34,4 @@ module ApiHelper 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) - # end - # TODO: not needed anymore end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 88f0736f..c45fd0af 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -23,6 +23,69 @@ RSpec.configure do |config| }, paths: {}, components: { + schemas: { + 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, diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml index cba34f7e..5fe3a5a3 100644 --- a/swagger/v1/swagger.yaml +++ b/swagger/v1/swagger.yaml @@ -12,10 +12,41 @@ paths: responses: '200': description: success + content: + application/json: + 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: + - id + - name + - email '401': description: not logged-in + content: + application/json: + schema: + "$ref": "#/components/schemas/Error401" '403': description: missing scope + content: + application/json: + schema: + "$ref": "#/components/schemas/Error403" "/user/financial_overview": get: summary: financial summary about the currently logged-in user @@ -25,11 +56,93 @@ paths: responses: '200': description: success + content: + application/json: + schema: + 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: 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 '401': description: not logged-in + content: + application/json: + schema: + "$ref": "#/components/schemas/Error401" '403': description: missing scope + content: + application/json: + schema: + "$ref": "#/components/schemas/Error403" components: + schemas: + 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