README.md in rspec-rails-api-0.5.0 vs README.md in rspec-rails-api-0.6.0

- old
+ new

@@ -70,12 +70,106 @@ end ``` ## Configuration -**TODO: This section is incomplete and the gem has no generator yet** +**TODO: This section is incomplete and the gem has no generator yet.** +Here is an example configuration: + +```rb +# spec/rails_helper.rb +require 'spec/support/rspec_rails_api' +``` + +```rb +# spec/support/acceptance_entities.rb + +# This file contains common object definitions +{ + error: { + error: { type: :string, description: "Error message" } + }, + form_error: { + title: { type: :array, required: false, description: "Title errors", of: :string } + }, +}.each do |name, attributes| + RSpec::Rails::Api::Metadata.add_entity name, attributes +end +``` + +```rb +# spec/support/rspec_rails_api.rb +require 'rspec_rails_api' +require 'support/acceptance_entities' + +# Include DSL in RSpec +RSpec.configure do |config| + config.include RSpec::Rails::Api::DSL::Example +end + +# Initialize and configure the renderer +renderer = RSpec::Rails::Api::OpenApiRenderer.new +# Server URL for quick reference +server_url = 'https://example.com' + +# Options here should be present for a valid OpenAPI file +renderer.api_title = 'MyProject API' +renderer.api_version = '1' + +# Options below are optional +# +# API description. Markdown supported +# renderer.api_description = 'Manage data on MyProject' +# +# List of servers, to live-test the documentation +# renderer.api_servers = [{ url: server_url }, { url: 'http://localhost:3000' }] +# +# Link to the API terms of service, if any +# renderer.api_tos = 'http://example.com/tos.html' +# +# Contact information +# renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http://example.com/contact' } +# +# API license information +# renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' } +# +# Possible security schemes +# renderer.add_security_scheme :pkce_code_grant, 'PKCE code grant', +# type: 'oauth2', +# flows: { +# implicit: { +# authorizationUrl: "#{server_url}/oauth/authorize", +# scopes: { read: 'will read data on your behalf', write: 'will write data on your behalf' } +# } +# } +# renderer.add_security_scheme :bearer, 'Bearer token', +# type: 'http', +# scheme: 'bearer' +# +# Declare keys whose values should be filtered in responses. +# renderer.redact_responses entity_name: { key: 'REDACTED' }, +# other_entity: { other_key: ['REDACTED'] } + + +# We need to merge each context metadata so we can reference to them to build the final file +RSpec.configuration.after(:context, type: :acceptance) do |context| + renderer.merge_context context.class.metadata[:rra].to_h + # During development of rspec_rails_api, you may want to dump raw metadata to a file + renderer.merge_context context.class.metadata[:rra].to_h, dump_metadata: true +end + +# Skip this block if you don't need the OpenAPI documentation file and only have your responses tested +RSpec.configuration.after(:suite) do + renderer.write_files Rails.root.join('public/swagger') # Write both YAML and prettified JSON files + # or + renderer.write_files Rails.root.join('public/swagger'), only: [:json] # Prettified JSON only + # or + renderer.write_files Rails.root.join('public/swagger'), only: [:yaml] # YAML only +end +``` + ### Integration with Devise To use `sign_in` and `sign_out` from Devise in the acceptance tests, create a Devise support file: ```ruby @@ -122,11 +216,11 @@ end # In examples #... for_code 200, 'Success' do |url| - sing_in #... + sign_in #... test_response_of url #... end #... @@ -147,11 +241,11 @@ The idea is to have a simple DSL, and declare things like: **spec/acceptance/users_spec.rb** ```ruby -require 'acceptance_helper' +require 'rails_helper' RSpec.describe 'Users', type: :acceptance do resource 'Users', 'Manage users' entity :user, @@ -184,10 +278,82 @@ end end end ``` +### Entity declarations + +You can declare entities locally (in every spec files), but sometimes you will need to use/reference the same entity +in multiple spec files (e.g.: an error message). In that case, you can create _global_ entities in separate files, and they +will be picked-up when needed. + +Example of a local entity: + +```rb +# spec/acceptance/api/users_acceptance_spec.rb +require 'rails_helper' + +RSpec.describe 'Users', type: :acceptance do + resource 'Users', 'Manage users' + + # This is a local entity + entity :user, + id: { type: :integer, description: 'The id' }, + email: { type: :string, description: 'The name' }, + role: { type: :string, description: 'The name' }, + created_at: { type: :datetime, description: 'Creation date' }, + updated_at: { type: :datetime, description: 'Modification date' }, + url: { type: :string, description: 'URL to this category' } + + on_get '/api/users/', 'Users list' do + for_code 200, 'Success response', expect_many: :user do |url| + test_response_of url + end + end + + #... +end +``` + +Defining global entities: + +```rb +# spec/support/entities/user.rb +# This file should be required at some point in the "rails_helper" or "acceptance_helper" + +require 'rspec/rails/api/metadata' + +RSpec::Rails::Api::Metadata.add_entity :user, + id: { type: :integer, description: 'The id' }, + email: { type: :string, description: 'The name' }, + role: { type: :string, description: 'The name' }, + created_at: { type: :datetime, description: 'Creation date' }, + updated_at: { type: :datetime, description: 'Modification date' }, + url: { type: :string, description: 'URL to this category' } + +``` + +Organization of the global entities declaration is up to you. + +```rb +# spec/acceptance/api/users_acceptance_spec.rb +require 'rails_helper' + +RSpec.describe 'Users', type: :acceptance do + resource 'Users', 'Manage users' + + on_get '/api/users/', 'Users list' do + # "user" will use the global user entity + for_code 200, 'Success response', expect_many: :user do |url| + test_response_of url + end + end + + #... +end +``` + ### DSL #### Example groups ##### `resource(type, description)` @@ -195,10 +361,27 @@ Starts a resource description. - It must be called before any other documentation calls. - It should be in the first `describe block` +A resource may be completed across multiple spec files: + +```rb +# an_acceptance_spec.rb +RSpec.describe 'Something', type: :acceptance do + resource 'User', 'Manage users' +end + +# another_acceptance_spec.rb +RSpec.describe 'Something else', type: :acceptance do + resource 'User', 'Another description' +end + +``` + +The first evaluated `resource` statement will be used as description; all the tests in both files will complete it. + ##### `entity(type, fields)` Describes an entity for the documentation. The type is only a reference, you can put whatever fits (i.e: `:account`, `:user`, ...). @@ -421,15 +604,34 @@ # ... end # ... ``` +##### `requires_security(scheme_references)` + +Specifies the valid security schemes to use for this request. Security schemes are declared at the renderer level +(see [the configuration example](#Configuration)). + +```rb +# Given a previously :basic scheme + +# ... + on_get '/some/path' do + require_security :basic, :implicit + + for_code 200 do |url| + #... + end + end +# ... +``` + #### Examples Example methods are available in `for_code` blocks -##### `test_response_of(example, path_params: {}, payload: {}, headers: {})` +##### `test_response_of(example, path_params: {}, payload: {}, headers: {}, ignore_content_type: false)` Visits the described URL and: - Expects the response code to match the described one - Expects the content type to be `application/json` @@ -439,9 +641,10 @@ - `path_params`: a hash of overrides for path params (useful if a custom value is needed) - `payload`: a hash of values to send. Ignored for GET and DELETE requests - `headers`: a hash of custom headers. +- `ignore_content_type`: whether to ignore response's content-type. By default, checks for a JSON response ```ruby for_code 200, 'Success' do |url| test_response_of url end