diff --git a/.reek.yml b/.reek.yml index 696f292b..f27c3305 100644 --- a/.reek.yml +++ b/.reek.yml @@ -117,7 +117,8 @@ detectors: - _ UnusedParameters: enabled: true - exclude: [] + exclude: + - DeviseTokenAuth::TokenFactory#self.create UnusedPrivateMethod: enabled: false UtilityFunction: diff --git a/.rubocop.yml b/.rubocop.yml index aaa6abd0..a0b16504 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -18,6 +18,10 @@ Metrics/AbcSize: - db/migrate/20161011151353_devise_create_users.rb - db/migrate/20181102142200_create_active_storage_tables.active_storage.rb +Lint/UnusedMethodArgument: + Exclude: + - spec/support/devise_token_auth_overrides.rb + Layout/LineLength: Exclude: - db/migrate/20161011184702_devise_create_admin_users.rb diff --git a/Gemfile b/Gemfile index 021caf07..c1daa404 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gem 'rails', '~> 6.0.0' gem 'activeadmin', '~> 2.8' gem 'active_storage_base64', '~> 1.0.0' gem 'arctic_admin', '~> 3.2' -gem 'aws-sdk-s3', '~> 1', require: false +gem 'aws-sdk-s3', '~> 1.75', require: false gem 'bootsnap', '~> 1.4', '>= 1.4.5' gem 'delayed_job_active_record', '~> 4.1', '>= 4.1.4' gem 'devise', '~> 4.7', '>= 4.7.2' @@ -39,6 +39,7 @@ group :development, :test do gem 'factory_bot_rails', '~> 5.1', '>= 5.1.1' gem 'pry-byebug', '~> 3.9', platform: :mri gem 'pry-rails', '~> 0.3.9' + gem 'rspec_api_documentation', '~> 6.1.0' gem 'rspec-rails', '~> 3.9' end diff --git a/Gemfile.lock b/Gemfile.lock index e57ee23b..95d07ee6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,21 +86,21 @@ GEM font-awesome-sass (~> 5.0) jquery-rails ast (2.4.1) - aws-eventstream (1.0.3) - aws-partitions (1.239.0) - aws-sdk-core (3.77.0) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-eventstream (1.1.0) + aws-partitions (1.379.0) + aws-sdk-core (3.109.0) + aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.36.0) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-kms (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.74.0) - aws-sdk-core (~> 3, >= 3.102.1) + aws-sdk-s3 (1.82.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.1) + aws-sigv4 (1.2.2) aws-eventstream (~> 1, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) @@ -246,6 +246,7 @@ GEM mini_portile2 (2.4.0) minitest (5.14.2) msgpack (1.3.1) + mustache (1.1.1) nio4r (2.5.4) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) @@ -340,6 +341,10 @@ GEM actionpack (>= 5.0) railties (>= 5.0) rexml (3.2.4) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) rspec-core (3.9.2) rspec-support (~> 3.9.3) rspec-expectations (3.9.2) @@ -357,6 +362,10 @@ GEM rspec-mocks (~> 3.9.0) rspec-support (~> 3.9.0) rspec-support (3.9.3) + rspec_api_documentation (6.1.0) + activesupport (>= 3.0.0) + mustache (~> 1.0, >= 0.99.4) + rspec (~> 3.0) rubocop (0.89.0) parallel (~> 1.10) parser (>= 2.7.1.1) @@ -450,7 +459,7 @@ DEPENDENCIES activeadmin (~> 2.8) annotate (~> 3.0, >= 3.0.3) arctic_admin (~> 3.2) - aws-sdk-s3 (~> 1) + aws-sdk-s3 (~> 1.75) better_errors (~> 2.5, >= 2.5.1) binding_of_caller (~> 0.8.0) bootsnap (~> 1.4, >= 1.4.5) @@ -480,6 +489,7 @@ DEPENDENCIES rails_best_practices (~> 1.19.4) reek (~> 5.5) rspec-rails (~> 3.9) + rspec_api_documentation (~> 6.1.0) rubocop-rails (~> 2.3, >= 2.3.2) rubocop-rootstrap (~> 0.1.2) sendgrid (~> 1.2.4) diff --git a/README.md b/README.md index 078c70df..13aab1da 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ This template comes with: - API documentation following https://apiblueprint.org/ - Docker support - Exception Tracking +- RSpec API Doc Generator ## How to use @@ -86,6 +87,7 @@ This template comes with: - [Rails Best Practices](https://github.com/flyerhzm/rails_best_practices) for rails linting - [Reek](https://github.com/troessner/reek) for ruby linting - [RSpec](https://github.com/rspec/rspec) for testing +- [Rspec API Doc Generator](https://github.com/zipmark/rspec_api_documentation) for API documentation - [Rubocop](https://github.com/bbatsov/rubocop/) for ruby linting - [Sendgrid](https://github.com/stephenb/sendgrid) for sending mails - [Shoulda Matchers](https://github.com/thoughtbot/shoulda-matchers) adds other testing matchers @@ -103,6 +105,15 @@ This template comes with: http://docs.railsapibase.apiary.io +With [Rspec API Doc Generator](https://github.com/zipmark/rspec_api_documentation) you can generate the docs after writing the acceptance specs. + +Just run: + +`./bin/docs ` + +An `apiary.apib` file will be generated at the root directory of the project. + + ## Code quality With `rake code_analysis` you can run the code analysis tool, you can omit rules with: diff --git a/apiary.apib b/apiary.apib index 8c7c6c9d..9209a0c6 100644 --- a/apiary.apib +++ b/apiary.apib @@ -1,221 +1,458 @@ FORMAT: 1A -HOST: http://rails5-api-base.herokuapp.com +# Rails API Template -# API BASE +# Group Passwords -Rails Api Base is a boilerplate project for JSON RESTful APIs. It follows the community best practices in terms of standards, security and maintainability, integrating a variety of testing and code quality tools. +## Create Password [api/v1/users/password] -## Users Collection [/api/v1/users] +### Create [POST] -### Sign Up [POST] ++ Request Bad -+ Request (application/json) - + Attributes - + user (Profile Edition, required) + + Headers + + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: kendrick@hayes.name + + + Body + + {"email":"notvalid@example.com"} + ++ Response 404 () + + + Body + + { + "error": "Unable to find user with email 'notvalid@example.com'." + } + ++ Request Ok + + + Headers + + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: allen@keebler.net + + + Body + + {"email":"allen@keebler.net"} + ++ Response 200 () + + + Body + + { + "success": true, + "message": "An email has been sent to 'allen@keebler.net' containing instructions for resetting your password." + } + +## Update Password [api/v1/users/password] + +### Update [PUT] + ++ Request Bad + + + Headers + + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: simone@kiehn.net + + + Body + {"password":"123456789","password_confirmation":""} + ++ Response 422 () -+ Response 200 (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + access-token: 1234567890123456789012 + token-type: Bearer + client: abcdefghijklmnopqrstuv + expiry: 1664993493 + uid: simone@kiehn.net + + Body - + Attributes - + user (Profile Response, required) + { + "success": false, + "errors": { + "password_confirmation": [ + "doesn't match Password" + ], + "full_messages": [ + "Password confirmation doesn't match Password" + ] + } + } -+ Response 401 ++ Request Ok + + Headers -## Current user's profile [/api/v1/user] + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: debroah.heidenreich@wilkinson-rutherford.net -### Get current user profile [GET] + + Body + + {"password":"123456789","password_confirmation":"123456789"} + ++ Response 200 () -+ Request (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - uid: test@test.com + access-token: 1234567890123456789012 + token-type: Bearer + client: abcdefghijklmnopqrstuv + expiry: 1664993493 + uid: debroah.heidenreich@wilkinson-rutherford.net + + Body + + { + "success": true, + "data": { + "id": 14, + "provider": "email", + "allow_password_change": false, + "email": "debroah.heidenreich@wilkinson-rutherford.net", + "uid": "debroah.heidenreich@wilkinson-rutherford.net", + "first_name": "Anitra Wunsch", + "last_name": "Treutel", + "username": "jeanne_koepp", + "created_at": "2020-10-05T18:11:32.928Z", + "updated_at": "2020-10-05T18:11:33.179Z" + }, + "message": "Your password has been successfully updated." + } + +## Edit Password [api/v1/users/password/edit] + +### Edit [GET] + ++ Request Ok -+ Response 200 (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: rayford.tillman@medhurst-kris.biz ++ Response 302 () - + Attributes - + user (Profile Response, required) + + Body + You are being redirected. -+ Response 401 +# Group Sessions +## Session [api/v1/users/sign_in] -### Update current user profile [PUT] +### Create [POST] + ++ Request Bad + + + Headers + + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: whitney_schultz@mraz-jakubowski.com + + + Body + + {"user":{"email":"whitney_schultz@mraz-jakubowski.com","password":"wrong-password"}} + ++ Response 401 () + + + Body + + { + "error": "Invalid login credentials. Please try again." + } + ++ Request Ok -+ Request (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - uid: test@test.com + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: saul.wolff@simonis.org + + + Body + + {"user":{"email":"saul.wolff@simonis.org","password":"0DgM3wJv"}} - + Attributes - + user (Profile Edition, required) ++ Response 200 () -+ Response 200 (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + access-token: 1234567890123456789012 + token-type: Bearer + client: abcdefghijklmnopqrstuv + expiry: 1664993490 + uid: saul.wolff@simonis.org + + Body - + Attributes - + user (Profile Response, required) + { + "user": { + "id": 1, + "email": "saul.wolff@simonis.org", + "uid": "saul.wolff@simonis.org", + "provider": "email", + "allow_password_change": false, + "first_name": "Rep. Vito Blick", + "last_name": "Blick", + "username": "alda.quigley", + "created_at": "2020-10-05T18:11:29.974Z", + "updated_at": "2020-10-05T18:11:30.288Z" + } + } -## Login [/api/v1/users/sign_in] +## Session [api/v1/users/sign_out] -### Login [POST] +### Delete [DELETE] -+ Request (application/json) ++ Request Bad + + + Headers + + Access-Token: notvalidtoken + Client: abcdefghijklmnopqrstuv + Uid: rob_schaden@bartell.name + ++ Response 404 () + Body { - "user": - { - "email": "test@gmail.com", - "password": "password" - } + "error": "User was not found or was not logged in." } ++ Request Ok -+ Response 200 (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: joaquin.pagac@weissnat-botsford.info ++ Response 200 () - + Attributes - + user (Profile Response, required) + + Body -+ Response 401 + { + "success": true + } + +# Group Settings + +## Must Update [api/v1/settings/must_update] -## Logout [/api/v1/users/sign_out] +### Get [GET] -### Logout [DELETE] ++ Request Bad -+ Request (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: leigh@schamberger.net -+ Response 200 (application/json) ++ Response 200 () + + Body -## Reset password [/api/v1/users/password] + { + "must_update": true + } -More information of how reset password works: https://github.com/lynndylanhurley/devise_token_auth/wiki/Reset-Password-Flow ++ Request Ok -### Reset password [POST] + + Headers -Use this route to send a password reset confirmation email + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: stephen.prosacco@glover.info + ++ Response 200 () -+ Request (application/json) + Body { - "email": "test@test.com", - "redirect_url": "http://www.example.com" + "must_update": false } -+ Response 200 (application/json) +# Group Status + +## Status [api/v1/status] + +### Get [GET] + ++ Request Ok + ++ Response 200 () + + Body { - "success": true, - "message": "An email has been sent to 'example@mail.com' containing instructions for resetting your password." + "online": true + } + +# Group Users + +## Create User [api/v1/users] + +### Create [POST] + ++ Request Bad + + + Headers + + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: chassidy_roob@willms.com + + + Body + + {"user":{"email":"not-valid","password":"HtOnJ6cH1dHo","username":"rosaria.anderson","first_name":"Milly Lang","last_name":"Rohan","uid":"9f4a3eea-9f7f-42fd-8dc1-5eca51131f1d"}} + ++ Response 422 () + + + Body + + { + "status": "error", + "data": { + "id": null, + "email": "not-valid", + "allow_password_change": false, + "first_name": "Milly Lang", + "last_name": "Rohan", + "username": "rosaria.anderson", + "created_at": null, + "updated_at": null, + "provider": "email", + "uid": "not-valid" + }, + "errors": { + "email": [ + "is not an email" + ], + "full_messages": [ + "Email is not an email" + ] + } } -### Reset password [PUT] ++ Request Ok -Use this route to change user's passwords + + Headers -+ Request (application/json) - + Parameters - + reset_password_token (string, required) + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: dallas@anderson.io + + + Body + + {"user":{"email":"ladonna@beier-kuvalis.co","password":"HjVvQzAh","username":"audrea","first_name":"Everett Brown","last_name":"Kirlin","uid":"cd14597d-91d4-4ae5-93b2-d8c6b4b12fd1"}} + ++ Response 200 () + + + Headers + + access-token: 1234567890123456789012 + token-type: Bearer + client: abcdefghijklmnopqrstuv + expiry: 1664993492 + uid: ladonna@beier-kuvalis.co + Body { - "password": "12345678", - "password_confirmation": "12345678" + "user": { + "id": 11, + "email": "ladonna@beier-kuvalis.co", + "allow_password_change": false, + "first_name": "Everett Brown", + "last_name": "Kirlin", + "username": "audrea", + "created_at": "2020-10-05T18:11:32.258Z", + "updated_at": "2020-10-05T18:11:32.363Z", + "provider": "email", + "uid": "ladonna@beier-kuvalis.co" + } } -+ Response 200 (application/json) +## Update User [api/v1/user] + +### Update [PUT] + ++ Request Ok + + + Headers + + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: pasty@bosco.co + + + Body + + {"user":{"username":"new username"}} + ++ Response 200 () + + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + access-token: 1234567890123456789012 + token-type: Bearer + client: abcdefghijklmnopqrstuv + expiry: 1664993491 + uid: pasty@bosco.co - + Attributes (Profile Response) + + Body -## Edit reset password [/api/v1/users/password/edit] + { + "user": { + "id": 7, + "email": "pasty@bosco.co", + "name": "Olin Senger Reilly", + "username": "new username" + } + } -### Edit reset password [GET] +## Show User [api/v1/user] -This route is the destination URL for password reset confirmation +### Show [GET] -+ Request (application/json) - + Parameters - + reset_password_token (string, required) - + redirect_url (string) ++ Request Ok -+ Response 200 (application/json) + Headers - access-token: sO2bm_Bpdyoo8r78jZ-fqg - client: QADgNCWRJj0LyRruqzYbBg - expiry: 1489009792 - uid: test@test.com + Access-Token: 1234567890123456789012 + Client: abcdefghijklmnopqrstuv + Uid: armando@purdy.co + ++ Response 200 () -# Data Structures + + Headers -## Profile Response (object) -+ id: 100 (number) -+ email: test@test.com (string) -+ provider: email, facebook, twitter (enum[string]) - it's way the user logins -+ uid: test@test.com (string) - the provider identifier -+ first_name: John (string) -+ last_name: Doe (string) -+ username: jdoe (string) -+ created_at: 2017-02-23T13:54:33.283Z (string) + access-token: 1234567890123456789012 + token-type: Bearer + client: abcdefghijklmnopqrstuv + expiry: 1664993491 + uid: armando@purdy.co -## Profile Edition (object) -+ email: test@test.com (string) -+ password: 12345678 (string) -+ first_name: John (string) -+ last_name: Doe (string) -+ username: jdoe (string) + + Body -## User (object) -+ email: test@test.com (string) -+ first_name: John (string) -+ last_name: Doe (string) -+ username: jdoe (string) + { + "user": { + "id": 8, + "email": "armando@purdy.co", + "name": "Sherie Cartwright CPA Schoen", + "username": "tai_kuvalis" + } + } diff --git a/app/views/api/v1/users/_info.json.jbuilder b/app/views/api/v1/users/_info.json.jbuilder index ec18bd75..1ea0260e 100644 --- a/app/views/api/v1/users/_info.json.jbuilder +++ b/app/views/api/v1/users/_info.json.jbuilder @@ -1,5 +1,4 @@ json.id user.id json.email user.email json.name user.full_name -json.first_name user.first_name json.username user.username diff --git a/bin/docs b/bin/docs new file mode 100755 index 00000000..ba2ee891 --- /dev/null +++ b/bin/docs @@ -0,0 +1,8 @@ +#!/bin/bash + +RAILS_ENV=test bundle exec rails db:schema:load +RAILS_ENV=test bundle exec rails docs:generate + +mv ./doc/api/index.apib ./apiary.apib + +rm -rf ./doc diff --git a/config/initializers/rspec_api_documentation.rb b/config/initializers/rspec_api_documentation.rb new file mode 100644 index 00000000..0893ce35 --- /dev/null +++ b/config/initializers/rspec_api_documentation.rb @@ -0,0 +1,100 @@ +unless Rails.env.production? + module RspecApiDocumentation + class RackTestClient < ClientBase + def response_body + last_response.body.encode('utf-8') + end + end + end + + # Values listed are the default values + RspecApiDocumentation.configure do |config| + # Set the application that Rack::Test uses + # config.app = Rails.application + + # Used to provide a configuration for the specification + # (supported only by 'open_api' format for now) + # config.configurations_dir = Rails.root.join("doc", "configurations", "api") + + # Output folder + # Careful! Use a dedicated folder cause its content will get deleted + # config.docs_dir = Rails.root.join("doc", "api") + + # An array of output format(s). + # Possible values are :json, :html, :combined_text, :combined_json, + # :json_iodocs, :textile, :markdown, :append_json, :slate, + # :api_blueprint, :open_api + config.format = [:api_blueprint] + + # Location of templates + # config.template_path = "inside of the gem" + + # Filter by example document type + # config.filter = :all + + # Filter by example document type + # config.exclusion_filter = nil + + # Used when adding a cURL output to the docs + # config.curl_host = nil + + # Used when adding a cURL output to the docs + # Allows you to filter out headers that are not needed in the cURL request, + # such as "Host" and "Cookie". Set as an array. + # config.curl_headers_to_filter = nil + + # By default, when these settings are nil, all headers are shown, + # which is sometimes too chatty. Setting the parameters to an + # array of headers will render *only* those headers. + config.request_headers_to_include = %w[access-token uid client] + config.response_headers_to_include = %w[access-token expiry token-type uid client] + + # By default examples and resources are ordered by description. Set to true keep + # the source order. + # config.keep_source_order = false + + # Change the name of the API on index pages + config.api_name = 'Rails API Template' + + # Change the description of the API on index pages + # config.api_explanation = "API Description" + + # Redefine what method the DSL thinks is the client + # This is useful if you need to `let` your own client, most likely a model. + # config.client_method = :client + + # Change the IODocs writer protocol + # config.io_docs_protocol = "http" + + # You can define documentation groups as well. A group allows you generate multiple + # sets of documentation. + # config.define_group :public do |config| + # # By default the group's doc_dir is a subfolder under the parent group, based + # # on the group's name. + # config.docs_dir = Rails.root.join("doc", "api", "public") + + # # Change the filter to only include :public examples + # config.filter = :public + # end + + # Change how the post body is formatted by default, you can still override by `raw_post` + # Can be :json, :xml, or a proc that will be passed the params + config.request_body_formatter = :json + + # Change how the response body is formatted by default + # Is proc that will be called with the response_content_type & response_body + # by default response_content_type of `application/json` are pretty formated. + # config.response_body_formatter = + # Proc.new { |response_content_type, response_body| response_body } + + # Change the embedded style for HTML output. This file will not be processed by + # RspecApiDocumentation and should be plain CSS. + # config.html_embedded_css_file = nil + + # Removes the DSL method `status`, this is required if you have a parameter named status + # config.disable_dsl_status! + + # Removes the DSL method `method`, this is required if you have a parameter named method + # config.disable_dsl_method! + end +end diff --git a/spec/acceptance/passwords_spec.rb b/spec/acceptance/passwords_spec.rb new file mode 100644 index 00000000..ac665606 --- /dev/null +++ b/spec/acceptance/passwords_spec.rb @@ -0,0 +1,68 @@ +require_relative '../support/acceptance_tests_helper' + +resource 'Passwords' do + header 'Content-Type', 'application/json' + header 'access-token', :access_token_header + header 'client', :client_header + header 'uid', :uid_header + + let(:user) { create(:user) } + + route 'api/v1/users/password', 'Create Password' do + post 'Create' do + example 'Ok' do + do_request(email: user.email) + + expect(status).to eq 200 + end + + example 'Bad' do + do_request(email: 'notvalid@example.com') + + expect(status).to eq 404 + end + end + end + + route 'api/v1/users/password/edit', 'Edit Password' do + let(:password_token) { user.send(:set_reset_password_token) } + let(:request) do + { + reset_password_token: password_token, + redirect_url: ENV['PASSWORD_RESET_URL'] + } + end + + get 'Edit' do + example 'Ok' do + do_request(request) + + expect(status).to eq 302 + end + end + end + + route 'api/v1/users/password', 'Update Password' do + let(:new_password) { '123456789' } + let(:request) do + { + password: new_password, + password_confirmation: new_password + } + end + + put 'Update' do + example 'Ok' do + do_request(request) + + expect(status).to eq 200 + end + + example 'Bad' do + do_request({ password: new_password, password_confirmation: '' }) + + expect(status).to eq 422 + end + end + end +end diff --git a/spec/acceptance/sessions_spec.rb b/spec/acceptance/sessions_spec.rb new file mode 100644 index 00000000..d516fd40 --- /dev/null +++ b/spec/acceptance/sessions_spec.rb @@ -0,0 +1,56 @@ +require_relative '../support/acceptance_tests_helper' + +resource 'Sessions' do + header 'Content-Type', 'application/json' + header 'access-token', :access_token_header + header 'client', :client_header + header 'uid', :uid_header + + let(:user) { create(:user) } + + route 'api/v1/users/sign_in', 'Session' do + let(:request) do + { + user: + { + email: user.email, + password: user.password + } + } + end + + post 'Create' do + example 'Ok' do + do_request(request) + + expect(status).to eq 200 + end + + example 'Bad' do + request[:user][:password] = 'wrong-password' + do_request(request) + + expect(status).to eq 401 + end + end + end + + route 'api/v1/users/sign_out', 'Session' do + let(:user) { create(:user) } + + delete 'Delete' do + example 'Ok' do + do_request + + expect(status).to eq 200 + end + + example 'Bad' do + auth_headers['access-token'] = 'notvalidtoken' + do_request + + expect(status).to eq 404 + end + end + end +end diff --git a/spec/acceptance/settings_spec.rb b/spec/acceptance/settings_spec.rb new file mode 100644 index 00000000..a8661066 --- /dev/null +++ b/spec/acceptance/settings_spec.rb @@ -0,0 +1,34 @@ +require_relative '../support/acceptance_tests_helper' + +resource 'Settings' do + header 'Content-Type', 'application/json' + header 'access-token', :access_token_header + header 'client', :client_header + header 'uid', :uid_header + + let(:user) { create(:user) } + + route 'api/v1/settings/must_update', 'Must Update' do + let(:request) do + { + device_version: '1.0' + } + end + + get 'Get' do + example 'Ok' do + create(:setting_version) + do_request(request) + + expect(status).to eq 200 + end + + example 'Bad' do + create(:setting_version, value: '2.0') + do_request(request) + + expect(status).to eq 200 + end + end + end +end diff --git a/spec/acceptance/status_spec.rb b/spec/acceptance/status_spec.rb new file mode 100644 index 00000000..91e5a9bd --- /dev/null +++ b/spec/acceptance/status_spec.rb @@ -0,0 +1,15 @@ +require_relative '../support/acceptance_tests_helper' + +resource 'Status' do + header 'Content-Type', 'application/json' + + route 'api/v1/status', 'Status' do + get 'Get' do + example 'Ok' do + do_request + + expect(status).to eq 200 + end + end + end +end diff --git a/spec/acceptance/users_spec.rb b/spec/acceptance/users_spec.rb new file mode 100644 index 00000000..cf0f2ea7 --- /dev/null +++ b/spec/acceptance/users_spec.rb @@ -0,0 +1,51 @@ +require_relative '../support/acceptance_tests_helper' + +resource 'Users' do + header 'Content-Type', 'application/json' + header 'access-token', :access_token_header + header 'client', :client_header + header 'uid', :uid_header + + let(:user) { create(:user) } + + route 'api/v1/users', 'Create User' do + let(:request) { { user: attributes_for(:user) } } + + post 'Create' do + example 'Ok' do + do_request(request) + + expect(status).to eq 200 + end + + example 'Bad' do + request[:user][:email] = 'not-valid' + do_request(request) + + expect(status).to eq 422 + end + end + end + + route 'api/v1/user', 'Show User' do + get 'Show' do + example 'Ok' do + do_request + + expect(status).to eq 200 + end + end + end + + route 'api/v1/user', 'Update User' do + let(:request) { { user: { username: 'new username' } } } + + put 'Update' do + example 'Ok' do + do_request(request) + + expect(status).to eq 200 + end + end + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 20ced3d2..cc652e5c 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -31,9 +31,11 @@ FactoryBot.define do factory :user do - email { Faker::Internet.unique.email } - password { Faker::Internet.password(min_length: 8) } - username { Faker::Internet.unique.user_name } - uid { Faker::Internet.uuid } + email { Faker::Internet.unique.email } + password { Faker::Internet.password(min_length: 8) } + username { Faker::Internet.unique.user_name } + first_name { Faker::Name.unique.name } + last_name { Faker::Name.unique.last_name } + uid { Faker::Internet.uuid } end end diff --git a/spec/helpers.rb b/spec/helpers.rb index 55e764c4..7e85a051 100644 --- a/spec/helpers.rb +++ b/spec/helpers.rb @@ -7,6 +7,18 @@ def json end def auth_headers - user.create_new_auth_token + @auth_headers ||= user.create_new_auth_token + end + + def uid_header + auth_headers['uid'] + end + + def access_token_header + auth_headers['access-token'] + end + + def client_header + auth_headers['client'] end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e2c10706..107b0334 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -42,7 +42,7 @@ end context 'when was created with regular login' do - let!(:user) { create(:user) } + let!(:user) { create(:user, first_name: nil, last_name: nil) } let(:full_name) { user.full_name } it 'returns the correct name' do diff --git a/spec/requests/api/v1/passwords/create_spec.rb b/spec/requests/api/v1/passwords/create_spec.rb index 711f5a6f..af3882e8 100644 --- a/spec/requests/api/v1/passwords/create_spec.rb +++ b/spec/requests/api/v1/passwords/create_spec.rb @@ -1,4 +1,4 @@ -describe 'POST api/v1/users/passwords', type: :request do +describe 'POST api/v1/users/password', type: :request do let!(:user) { create(:user, password: 'mypass123') } context 'with valid params' do diff --git a/spec/requests/api/v1/users/show_spec.rb b/spec/requests/api/v1/users/show_spec.rb index 9f97e1b5..e52745b4 100644 --- a/spec/requests/api/v1/users/show_spec.rb +++ b/spec/requests/api/v1/users/show_spec.rb @@ -11,7 +11,7 @@ subject expect(json[:user][:id]).to eq(user.id) - expect(json[:user][:first_name]).to eq(user.first_name) + expect(json[:user][:name]).to eq(user.full_name) end context 'when record is not found' do diff --git a/spec/requests/api/v1/users/update_spec.rb b/spec/requests/api/v1/users/update_spec.rb index a54bff91..6c651eeb 100644 --- a/spec/requests/api/v1/users/update_spec.rb +++ b/spec/requests/api/v1/users/update_spec.rb @@ -19,7 +19,7 @@ put api_v1_user_path, params: params, headers: auth_headers, as: :json expect(json[:user][:id]).to eq user.id - expect(json[:user][:first_name]).to eq user.first_name + expect(json[:user][:name]).to eq user.full_name end end diff --git a/spec/support/acceptance_tests_helper.rb b/spec/support/acceptance_tests_helper.rb new file mode 100644 index 00000000..c1597ad9 --- /dev/null +++ b/spec/support/acceptance_tests_helper.rb @@ -0,0 +1,3 @@ +require 'rails_helper' +require 'rspec_api_documentation/dsl' +require_relative 'devise_token_auth_overrides' diff --git a/spec/support/devise_token_auth_overrides.rb b/spec/support/devise_token_auth_overrides.rb new file mode 100644 index 00000000..2e98a926 --- /dev/null +++ b/spec/support/devise_token_auth_overrides.rb @@ -0,0 +1,12 @@ +module DeviseTokenAuth + module TokenFactory + def self.create(client: nil, lifespan: nil, cost: nil) + obj_client = 'abcdefghijklmnopqrstuv' + obj_token = '1234567890123456789012' + obj_token_hash = token_hash(obj_token, cost) + obj_expiry = expiry(lifespan) + + Token.new(obj_client, obj_token, obj_token_hash, obj_expiry) + end + end +end