README.md in rspec-rails-api-0.1.5 vs README.md in rspec-rails-api-0.2.0

- old
+ new

@@ -1,6 +1,6 @@ -# RSpecRailsApiDoc +# RSpec-rails-api > An RSpec plugin to test Rails api responses and generate swagger > documentation **This is a work in progress** but you're welcome to help, test, submit @@ -11,11 +11,11 @@ As the gem is not yet published, you have to specify its git repository in order to test it. Add this line to your application's Gemfile: -```rb +```ruby gem 'rspec-rails-api' ``` And then execute: @@ -27,26 +27,28 @@ Configuration should be made manually for now: **spec/acceptance_helper.rb** -```rb +```ruby require 'rails_helper' require 'rspec_rails_api' RSpec.configure do |config| config.include RSpec::Rails::Api::DSL::Example end renderer = RSpec::Rails::Api::OpenApiRenderer.new -renderer.api_servers = [{ url: 'https://example.com' }] -renderer.api_title = 'A nice API for a nice application' +# Options here should be customized +renderer.api_title = 'YourProject API' renderer.api_version = '1' -renderer.api_description = 'Access update data in this project' -# renderer.api_tos = 'http://example.com/tos.html' -# renderer.api_contact = { name: 'Admin', email: 'admin@example.com', 'http://example.com/contact' } -# renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' } +renderer.api_description = 'Manage data on YourProject' +# Options below are optional +renderer.api_servers = [{ url: 'https://example.com' }] +renderer.api_tos = 'http://example.com/tos.html' +renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http://example.com/contact' } +renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' } RSpec.configuration.after(:context, type: :acceptance) do |context| renderer.merge_context context.class.metadata[:rrad].to_h end @@ -56,18 +58,18 @@ end ``` **spec/rails_helper.rb** -```rb +```ruby # ... RSpec::Rails::DIRECTORY_MAPPINGS[:acceptance] = %w[spec acceptance] RSpec.configure do |config| # ... - config.include RSpec::Rails::RequestExampleGroup, :type => :acceptance + config.include RSpec::Rails::RequestExampleGroup, type: :acceptance end ``` ## Configuration @@ -75,11 +77,11 @@ ### Integration with Devise To use `sign_in` and `sign_out` from Devise in the acceptance tests, create a Devise support file: -```rb +```ruby # spec/support/devise.rb module DeviseAcceptanceSpecHelpers include Warden::Test::Helpers def sign_in(resource_or_scope, resource = nil) @@ -95,38 +97,38 @@ end ``` Load this file in `rails_helper.rb`: -```rb +```ruby #... # Add additional requires below this line. Rails is not loaded until this point! require 'support/devise' #... ``` Include the helper for acceptance specs: -```rb +```ruby RSpec.configure do |config| config.include DeviseAcceptanceSpecHelpers, type: :acceptance end ``` You can now use the methods as usual: -```rb +```ruby # In a before block before do sign_in #... end # In examples #... - for_code 200, 'Success' do |example| + for_code 200, 'Success' do |url| sing_in #... - visit example + visit url #... end #... ``` @@ -140,28 +142,24 @@ If you want to generate the documentation without testing the endpoints (and thus, without examples in generated files), use the `DOC_ONLY` environment variable: -```rb +```sh DOC_ONLY=true bundle exec rails spec ``` -For now, files are saved as `tmp/out.json` and `tmp/out.yml`. - -There is nothing to customize the file headers (info, license, ...) yet. - ## Writing specs -There is a [commented example](examples/commented.rb) available in -`doc/`. +There is a [commented example](dummy/spec/acceptance/posts_spec.rb) available in +`dummy/spec/acceptance`. The idea is to have a simple DSL, and declare things like: **spec/acceptance/users_spec.rb** -```rb +```ruby require 'acceptance_helper' RSpec.describe 'Users', type: :acceptance do resource 'Users', 'Manage users' @@ -172,12 +170,12 @@ 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' do |example| - visit example + for_code 200, 'Success response' do |url| + visit url expect(response).to have_many defined :user end end on_put '/api/users/:id', 'Users list' do @@ -189,41 +187,40 @@ email: { type: :string, required: false, description: 'New email' }, role: { type: :string, required: false, description: 'New role' }, } } - for_code 200, 'Success response' do |example| - visit example + for_code 200, 'Success response' do |url| + visit url expect(response).to have_one defined :user end end end ``` ### DSL #### Example groups -##### `resource(name, description)` +##### `resource(type, description)` Starts a resource description. - It must be called before any other documentation calls. - It should be in the first `describe block` -##### `entity(name, fields)` +##### `entity(type, fields)` -Describes an entity for the documentation. The name is not visible, so -you can put whatever fits (i.e: `:account`, `:user` if the content -differs) +Describes an entity for the documentation. The type is only a reference, +you can put whatever fits (i.e: `:account`, `:user`, ...). -They are ideally in the main `describe` block. +They should be in the main `describe` block. -- `name` is a symbol +- `type` is a symbol - `description` is a hash of attributes -```rb +```ruby { id: { type: :integer, desc: 'The resource identifier' }, name: { type: :string, desc: 'The resource name' }, # ... } @@ -251,11 +248,11 @@ ###### Objects and arrays To describe complex structures, use `:object` with `:attributes` and `:array` `:of` something: -```rb +```ruby entity :friend, name: { type: :string, required: false, description: 'Friend name' } entity :user, id: { type: :number, required: false, description: 'Identifier' }, @@ -275,10 +272,19 @@ inline. Both `:of` and `attributes` may be a hash of fields or a symbol. If they are omitted, they will be documented, but responses won't be validated. +##### `parameters(type, fields)` +Describe path or request parameters. The type is only a reference, +use whatever makes sense. These parameters will be present in +documentation, only if they are referenced by a `request_params` or +`path_params` call. + +Fields have the structure of the hash you would give to `request_params` +or `path_params` (see each method later in this documentation). + ##### `on_<xxx>(url, description, &block)` Defines an URL. - `url` should be a relative URL to an existing endpoint (i.e.: @@ -292,41 +298,57 @@ - `on_post` - `on_put` - `on_patch` - `on_delete` -##### `path_params(<hash_of_attributes>)` +##### `path_params(fields: nil, defined: nil)` Defines the path parameters that are used in the URL. -```rb +```ruby on_get '/api/users/:id/posts/:post_slug?full=:full_post' do - path_params id: type: :integer, description: 'The user ID', - post_slug: type: :string, description: 'The post slug', - full_post: type: :boolean, required: false, description: 'Returns the full post if `true`, or only an excerpt' + path_params fields: { + id: { type: :integer, description: 'The user ID' }, + post_slug: { type: :string, description: 'The post slug' }, + full_post: { type: :boolean, required: false, description: 'Returns the full post if `true`, or only an excerpt' } } # ... end ``` - `type` is the field type (check _entity definition_ for a list). - `description` should be some valid [CommonMark](https://commonmark.org/) - `required` is optional an defaults to `true`. -##### `request_params(<hash_of_attributes>)` +Alternative with defined parameters: +```ruby +parameters :users_post_path_params, + id: { type: :integer, description: 'The user ID' }, + post_slug: { type: :string, description: 'The post slug' } + +on_get '/api/users/:id/posts/:post_slug' do + path_params defined: :users_post_path_params + + #... +end +``` + +##### `request_params(attributes: nil, defined: nil)` + Defines the format of the JSON payload. Type `object` is supported, so nested elements can be described: -```rb +```ruby on_post '/api/items' do - request_params item: { type: :object, required: true, properties: { - name: { type: integer, description: 'The name of the new item', required: true }, - notes: { type: string, description: 'Additional notes' } - }, - } + request_params attributes: { + item: { type: :object, required: true, properties: { + name: { type: integer, description: 'The name of the new item', required: true }, + notes: { type: string, description: 'Additional notes' } + } } + } #... end ``` An attribute should have the following form: @@ -340,10 +362,27 @@ `type` can be `:object` if the attribute contains other attributes. - `required` is optional an defaults to `false`. - `properties` is a hash of params and is only used if `type: :object` - `of` is a hash of params and is only used if `type: :array` +Alternative with defined parameters: + +```ruby +parameters :item_form_params, + item: { type: :object, required: true, properties: { + name: { type: integer, description: 'The name of the new item', required: true }, + notes: { type: string, description: 'Additional notes' } + } + } + +on_post '/api/items' do + request_params defined: :item_form_params + + #... +end +``` + ##### `for_code(http_status, description, doc_only: false &block)` Describes the desired output for a precedently defined URL. Block takes one required argument, that should be passed to `visit`. @@ -365,14 +404,14 @@ to test. Once again, you have to pass an argument to the block if you use `visit`. -```rb +```ruby # ... - for_code 200 'A successful response' do |example| - visit example + for_code 200, 'A successful response' do |url| + visit url # ... end # ... ``` @@ -393,13 +432,13 @@ value is needed) - `payload`: a hash of values to send. Ignored for GET and DELETE requests - `headers`: a hash of custom headers. -```rb -for_code 200, 'Success' do |example| - visit example +```ruby +for_code 200, 'Success' do |url| + visit url end ``` #### Matchers @@ -408,13 +447,13 @@ Expects the compared content to be a hash with the same keys as a defined entity. It should be compared against a hash or a `response` object: -```rb +```ruby #... -entity user: +entity :user, id: { type: :integer, desc: 'The id' }, name: { type: :string, desc: 'The name' } #... @@ -431,15 +470,15 @@ Expects the compared content to be an array of hashes with the same keys as a defined entity. It should be compared against an array or a `response` object: -```rb +```ruby #... -entity user: +entity :user, id: { type: :integer, desc: 'The id' }, - name: { type: :string, desc: 'The name }' + name: { type: :string, desc: 'The name' } #... expect([{id: 2, name: 'Jessica'}, {name: 'John'}]).to have_many defined :user # Fails because `id` is missing in the second entry @@ -454,11 +493,11 @@ ### Contexts Contexts will break the thing. This is due to how the gem builds its metadata, relying on the parents metadata. You have to stick to the DSL. -```rb +```ruby RSpec.describe 'Categories', type: :request do describe 'Categories' context 'Authenticated' do on_get '/api/categories', 'List all categories' do @@ -492,17 +531,41 @@ There is no support for file fields yet. ## Development After checking out the repo, run `bin/setup` to install dependencies. -Then, run `rake spec` to run the tests. You can also run `bin/console` +Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. -To install this gem onto your local machine, run `bundle exec rake -install`. To release a new version, update the version number in -`version.rb`, and then run `bundle exec rake release`, which will create -a git tag for the version, push git commits and tags, and push the -`.gem` file to [rubygems.org](https://rubygems.org). +### Testing + +#### Dummy application + +A small Rails application is available as an example, in the `dummy` directory. +If you write new features in the library, you'll have to update the examples to +make them pass their tests: + +```shell +cd dummy +bundle exec rspec +``` + +Doing so will also update the fixtures used by some of the library tests. + +Please don't commit the fixtures unless the changes in them are directly related +to the changes in the library. + +#### Code linting + +We use Rubocop here, with a releset for the library, and another for the dummy +application: + +```shell +bundle exec rubocop + +cd dummy +bundle exec rubocop +``` ## Contributing Bug reports and pull requests are welcome on GitLab at https://gitlab.com/experimentslabs/rspec-rails-api/issues. This