From 157f795855d3f31bf148e02f7a4c3af124ec598d Mon Sep 17 00:00:00 2001 From: eduardryazapov Date: Thu, 27 Sep 2018 17:06:44 +0300 Subject: [PATCH 1/2] expect response body in specs --- .rubocop.yml | 3 + Gemfile | 4 +- Gemfile.lock | 17 +- app/controllers/v1/base_controller.rb | 5 + app/models/.gitkeep | 0 app/models/current.rb | 3 + bin/ci | 2 +- doc/api/v1/profiles/delete_profile.md | 14 +- doc/api/v1/profiles/retrive_profile.md | 12 +- doc/api/v1/profiles/update_profile.md | 18 +- ...e_with_empty_password_and_invalid_email.md | 14 +- doc/api/v1/registration/create_user.md | 12 +- doc/api/v1/tokens/create_token.md | 12 +- .../create_token_with_invalid_password.md | 8 +- doc/api/v1/users/list_users.md | 30 +- doc/api/v1/users/retrive_user.md | 16 +- .../v1/users/retrive_user_with_invalid_id.md | 6 +- spec/api/v1/profiles_spec.rb | 97 ++++- spec/api/v1/registrations_spec.rb | 34 +- spec/api/v1/tokens_spec.rb | 46 ++- spec/api/v1/users_spec.rb | 76 +++- spec/interactors/create_jwt_spec.rb | 36 +- spec/models/error_spec.rb | 8 +- spec/models/jwt_token_spec.rb | 2 +- spec/schemas/v1/definitions.json | 30 -- spec/schemas/v1/errors.json | 5 - spec/schemas/v1/jsonapi.json | 381 ------------------ spec/schemas/v1/jwt_token.json | 10 - spec/schemas/v1/user.json | 10 - spec/schemas/v1/users.json | 12 - spec/support/helpers.rb | 5 + spec/support/helpers/response_helper.rb | 5 + spec/support/json_matchers.rb | 3 - .../shared_contexts/json_api_headers.rb | 13 +- .../support/shared_contexts/time_is_frozen.rb | 7 + .../api_endpoint_with_authorization.rb | 23 +- 36 files changed, 356 insertions(+), 623 deletions(-) delete mode 100644 app/models/.gitkeep create mode 100644 app/models/current.rb delete mode 100644 spec/schemas/v1/definitions.json delete mode 100644 spec/schemas/v1/errors.json delete mode 100644 spec/schemas/v1/jsonapi.json delete mode 100644 spec/schemas/v1/jwt_token.json delete mode 100644 spec/schemas/v1/user.json delete mode 100644 spec/schemas/v1/users.json create mode 100644 spec/support/helpers.rb create mode 100644 spec/support/helpers/response_helper.rb delete mode 100644 spec/support/json_matchers.rb create mode 100644 spec/support/shared_contexts/time_is_frozen.rb diff --git a/.rubocop.yml b/.rubocop.yml index d1e9f1f..1645fdd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -34,6 +34,9 @@ RSpec/MultipleExpectations: RSpec/ExampleLength: Enabled: false +RSpec/ImplicitSubject: + Enabled: false + RSpec/RepeatedDescription: Enabled: false diff --git a/Gemfile b/Gemfile index 27fa75a..9357318 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ group :development, :test do gem "bundler-audit" gem "byebug" gem "dotenv-rails" - gem "rspec-its" + gem "pry-rails" gem "rspec-rails" gem "rubocop" gem "rubocop-rspec" @@ -43,9 +43,9 @@ end group :test do gem "email_spec" - gem "json_matchers" gem "rspec_api_documentation" gem "shoulda-matchers", require: false + gem "timecop" gem "webmock", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 5281559..da8c225 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,6 +66,7 @@ GEM case_transform (0.2) activesupport choice (0.2.0) + coderay (1.1.2) concurrent-ruby (1.0.5) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -100,9 +101,6 @@ GEM concurrent-ruby (~> 1.0) interactor (3.1.1) jaro_winkler (1.5.1) - json_matchers (0.10.0) - json_schema - json_schema (0.19.1) jsonapi-renderer (0.2.0) jwt (1.5.6) kaminari (1.1.1) @@ -152,6 +150,11 @@ GEM ast (~> 2.4.0) pg (1.1.3) powerpack (0.1.2) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) public_suffix (3.0.3) puma (3.12.0) rack (2.0.5) @@ -206,9 +209,6 @@ GEM rspec-expectations (3.8.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) - rspec-its (1.2.0) - rspec-core (>= 3.0.0) - rspec-expectations (>= 3.0.0) rspec-mocks (3.8.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) @@ -258,6 +258,7 @@ GEM sprockets (>= 3.0.0) thor (0.19.4) thread_safe (0.3.6) + timecop (0.9.1) tzinfo (1.2.5) thread_safe (~> 0.1) unicode-display_width (1.4.0) @@ -288,18 +289,17 @@ DEPENDENCIES faker health_check interactor - json_matchers kaminari knock letter_opener pg + pry-rails puma rack-cors rails (= 5.2.1) rails-erd responders rollbar - rspec-its rspec-rails rspec_api_documentation rubocop @@ -309,6 +309,7 @@ DEPENDENCIES spring spring-commands-rspec spring-watcher-listen + timecop webmock RUBY VERSION diff --git a/app/controllers/v1/base_controller.rb b/app/controllers/v1/base_controller.rb index 672e8d8..996bc63 100644 --- a/app/controllers/v1/base_controller.rb +++ b/app/controllers/v1/base_controller.rb @@ -3,6 +3,7 @@ class BaseController < ActionController::API include Knock::Authenticable before_action :authenticate_user! + before_action :setup_currents rescue_from ActiveRecord::RecordNotFound do |_exception| respond_with_error(:record_not_found) @@ -18,6 +19,10 @@ def current_user @current_user ||= token && authenticate_for(User) end + def setup_currents + Current.user = current_user + end + def respond_with_resource(resource, status: :ok, location: resource, include: nil, fields: nil) render jsonapi: resource, include: include, status: status, location: location, fields: fields end diff --git a/app/models/.gitkeep b/app/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/current.rb b/app/models/current.rb new file mode 100644 index 0000000..73a9744 --- /dev/null +++ b/app/models/current.rb @@ -0,0 +1,3 @@ +class Current < ActiveSupport::CurrentAttributes + attribute :user +end diff --git a/bin/ci b/bin/ci index 7657bc6..d63992b 100755 --- a/bin/ci +++ b/bin/ci @@ -2,5 +2,5 @@ set -e -bin/rspec spec bin/quality +bin/rspec spec diff --git a/doc/api/v1/profiles/delete_profile.md b/doc/api/v1/profiles/delete_profile.md index eaa67da..28a541d 100644 --- a/doc/api/v1/profiles/delete_profile.md +++ b/doc/api/v1/profiles/delete_profile.md @@ -9,7 +9,7 @@
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTY2fQ.qgWchRvG9luv0IVp-sl-xfmFaIPtwTKcwttUozDUdcI
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM3fQ.mtEM7fCTtzOE4U8ga4EJlDR-1h8Wul-0pwP1qULyn78 #### Route @@ -17,14 +17,14 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO #### Body -
{"data":{"type":"profile_requests","attributes":{"full_name":null,"email":null,"password":null}}}
+
{}
#### cURL -
curl "http://localhost:5000/v1/profile" -d '{"data":{"type":"profile_requests","attributes":{"full_name":null,"email":null,"password":null}}}' -X DELETE \
+
curl "http://localhost:5000/v1/profile" -d '{}' -X DELETE \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTY2fQ.qgWchRvG9luv0IVp-sl-xfmFaIPtwTKcwttUozDUdcI"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM3fQ.mtEM7fCTtzOE4U8ga4EJlDR-1h8Wul-0pwP1qULyn78"
### Response @@ -40,11 +40,11 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO
{
   "data": {
-    "id": "166",
+    "id": "337",
     "type": "users",
     "attributes": {
-      "email": "user4@example.com",
-      "full_name": "Dr. Joana Heathcote"
+      "email": "john.smith@example.com",
+      "full_name": "John Smith"
     }
   }
 }
diff --git a/doc/api/v1/profiles/retrive_profile.md b/doc/api/v1/profiles/retrive_profile.md index c31b0c7..1061649 100644 --- a/doc/api/v1/profiles/retrive_profile.md +++ b/doc/api/v1/profiles/retrive_profile.md @@ -9,7 +9,7 @@
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODcsInN1YiI6MTYzfQ.S1ejGuAOao-cIYvsSnbaETAG-1UCEtDZcDsdV-SeETs
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM0fQ.KXLuWGfolqLxJID0sHfTsSB__K-Mg6w8fJZosAO1Ca8 #### Route @@ -17,14 +17,14 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO #### Query Parameters -
{"data":{"type":"profile_requests","attributes":{"full_name":null,"email":null,"password":null}}}: 
+
{}: 
#### cURL
curl -g "http://localhost:5000/v1/profile" -X GET \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODcsInN1YiI6MTYzfQ.S1ejGuAOao-cIYvsSnbaETAG-1UCEtDZcDsdV-SeETs"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM0fQ.KXLuWGfolqLxJID0sHfTsSB__K-Mg6w8fJZosAO1Ca8" ### Response @@ -40,11 +40,11 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO
{
   "data": {
-    "id": "163",
+    "id": "334",
     "type": "users",
     "attributes": {
-      "email": "user1@example.com",
-      "full_name": "Joe Marks II"
+      "email": "john.smith@example.com",
+      "full_name": "John Smith"
     }
   }
 }
diff --git a/doc/api/v1/profiles/update_profile.md b/doc/api/v1/profiles/update_profile.md index 9bc5291..c025bf3 100644 --- a/doc/api/v1/profiles/update_profile.md +++ b/doc/api/v1/profiles/update_profile.md @@ -8,9 +8,9 @@ | Name | Description | Required | Scope | |------|-------------|----------|-------| -| full_name | full name | false | | -| email | email | false | | -| password | password | false | | +| full_name | full name | false | data[attributes] | +| email | email | false | data[attributes] | +| password | password | false | data[attributes] | ### Request @@ -18,7 +18,7 @@
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODcsInN1YiI6MTY0fQ.khNxDZk0-l7OVUIJEdr7JVmYE8oCmrH8WaorMAWk9Oc
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM1fQ.ywxsXPwLpOAce6cJ13fAabrqzKrvnWeMS2dDCw7dHlc #### Route @@ -26,14 +26,14 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO #### Body -
{"data":{"type":"profile_requests","attributes":{"full_name":"Example User Updated","email":"user_updated@example.com","password":"new_password"}}}
+
{"data":{"attributes":{"full_name":"Updated Name","email":"user_updated@example.com","password":"new_password"}}}
#### cURL -
curl "http://localhost:5000/v1/profile" -d '{"data":{"type":"profile_requests","attributes":{"full_name":"Example User Updated","email":"user_updated@example.com","password":"new_password"}}}' -X PATCH \
+
curl "http://localhost:5000/v1/profile" -d '{"data":{"attributes":{"full_name":"Updated Name","email":"user_updated@example.com","password":"new_password"}}}' -X PATCH \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODcsInN1YiI6MTY0fQ.khNxDZk0-l7OVUIJEdr7JVmYE8oCmrH8WaorMAWk9Oc"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM1fQ.ywxsXPwLpOAce6cJ13fAabrqzKrvnWeMS2dDCw7dHlc"
### Response @@ -49,11 +49,11 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO
{
   "data": {
-    "id": "164",
+    "id": "335",
     "type": "users",
     "attributes": {
       "email": "user_updated@example.com",
-      "full_name": "Example User Updated"
+      "full_name": "Updated Name"
     }
   }
 }
diff --git a/doc/api/v1/profiles/update_profile_with_empty_password_and_invalid_email.md b/doc/api/v1/profiles/update_profile_with_empty_password_and_invalid_email.md index 1f68e7c..d21706d 100644 --- a/doc/api/v1/profiles/update_profile_with_empty_password_and_invalid_email.md +++ b/doc/api/v1/profiles/update_profile_with_empty_password_and_invalid_email.md @@ -8,9 +8,9 @@ | Name | Description | Required | Scope | |------|-------------|----------|-------| -| full_name | full name | false | | -| email | email | false | | -| password | password | false | | +| full_name | full name | false | data[attributes] | +| email | email | false | data[attributes] | +| password | password | false | data[attributes] | ### Request @@ -18,7 +18,7 @@
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTY1fQ.AoMwSorPTETdEFEM1CUTKbEKEQzKhTGRW_yxh4KDPL0
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM2fQ.AyXUoKLR8wF8zTvflSC1r_hWtUy3EhpbzaxA095JhPs #### Route @@ -26,14 +26,14 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO #### Body -
{"data":{"type":"profile_requests","attributes":{"full_name":"Example User Updated","email":"invalid","password":""}}}
+
{"data":{"attributes":{"full_name":"Updated Name","email":"invalid","password":""}}}
#### cURL -
curl "http://localhost:5000/v1/profile" -d '{"data":{"type":"profile_requests","attributes":{"full_name":"Example User Updated","email":"invalid","password":""}}}' -X PATCH \
+
curl "http://localhost:5000/v1/profile" -d '{"data":{"attributes":{"full_name":"Updated Name","email":"invalid","password":""}}}' -X PATCH \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTY1fQ.AoMwSorPTETdEFEM1CUTKbEKEQzKhTGRW_yxh4KDPL0"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM2fQ.AyXUoKLR8wF8zTvflSC1r_hWtUy3EhpbzaxA095JhPs"
### Response diff --git a/doc/api/v1/registration/create_user.md b/doc/api/v1/registration/create_user.md index ebe4569..4edbbfc 100644 --- a/doc/api/v1/registration/create_user.md +++ b/doc/api/v1/registration/create_user.md @@ -8,9 +8,9 @@ | Name | Description | Required | Scope | |------|-------------|----------|-------| -| full_name | full name | false | | -| email | email | true | | -| password | password | true | | +| full_name | full name | false | data[attributes] | +| email | email | true | data[attributes] | +| password | password | true | data[attributes] | ### Request @@ -25,11 +25,11 @@ Accept: application/vnd.api+json #### Body -
{"data":{"type":"registration_requests","attributes":{"full_name":"Example User","email":"user@example.com","password":"123456"}}}
+
{"data":{"attributes":{"full_name":"Example User","email":"user@example.com","password":"123456"}}}
#### cURL -
curl "http://localhost:5000/v1/registrations" -d '{"data":{"type":"registration_requests","attributes":{"full_name":"Example User","email":"user@example.com","password":"123456"}}}' -X POST \
+
curl "http://localhost:5000/v1/registrations" -d '{"data":{"attributes":{"full_name":"Example User","email":"user@example.com","password":"123456"}}}' -X POST \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json"
@@ -47,7 +47,7 @@ Accept: application/vnd.api+json
{
   "data": {
-    "id": "167",
+    "id": "338",
     "type": "users",
     "attributes": {
       "email": "user@example.com",
diff --git a/doc/api/v1/tokens/create_token.md b/doc/api/v1/tokens/create_token.md
index be71e87..ac11326 100644
--- a/doc/api/v1/tokens/create_token.md
+++ b/doc/api/v1/tokens/create_token.md
@@ -8,8 +8,8 @@
 
 | Name | Description | Required | Scope |
 |------|-------------|----------|-------|
-| email | email | true |  |
-| password | password | true |  |
+| email | email | true | data[attributes] |
+| password | password | true | data[attributes] |
 
 ### Request
 
@@ -24,11 +24,11 @@ Accept: application/vnd.api+json
#### Body -
{"data":{"type":"token_requests","attributes":{"email":"user@example.com","password":"123456"}}}
+
{"data":{"attributes":{"email":"user@example.com","password":"123456"}}}
#### cURL -
curl "http://localhost:5000/v1/tokens" -d '{"data":{"type":"token_requests","attributes":{"email":"user@example.com","password":"123456"}}}' -X POST \
+
curl "http://localhost:5000/v1/tokens" -d '{"data":{"attributes":{"email":"user@example.com","password":"123456"}}}' -X POST \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json"
@@ -46,10 +46,10 @@ Accept: application/vnd.api+json
{
   "data": {
-    "id": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTY4fQ.42J7f4PUabczP3pmUpQNcmtG4GFmXpHA17VaWmnQqK0",
+    "id": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM5fQ.JcMnxgvF9ysWq7nUpnoDHS4GhCCkTxiERPr6JQQGsHA",
     "type": "jwt_tokens",
     "attributes": {
-      "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTY4fQ.42J7f4PUabczP3pmUpQNcmtG4GFmXpHA17VaWmnQqK0"
+      "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzM5fQ.JcMnxgvF9ysWq7nUpnoDHS4GhCCkTxiERPr6JQQGsHA"
     }
   }
 }
diff --git a/doc/api/v1/tokens/create_token_with_invalid_password.md b/doc/api/v1/tokens/create_token_with_invalid_password.md index d65086d..0445201 100644 --- a/doc/api/v1/tokens/create_token_with_invalid_password.md +++ b/doc/api/v1/tokens/create_token_with_invalid_password.md @@ -8,8 +8,8 @@ | Name | Description | Required | Scope | |------|-------------|----------|-------| -| email | email | true | | -| password | password | true | | +| email | email | true | data[attributes] | +| password | password | true | data[attributes] | ### Request @@ -24,11 +24,11 @@ Accept: application/vnd.api+json #### Body -
{"data":{"type":"token_requests","attributes":{"email":"user@example.com","password":"invalid"}}}
+
{"data":{"attributes":{"email":"user@example.com","password":"invalid"}}}
#### cURL -
curl "http://localhost:5000/v1/tokens" -d '{"data":{"type":"token_requests","attributes":{"email":"user@example.com","password":"invalid"}}}' -X POST \
+
curl "http://localhost:5000/v1/tokens" -d '{"data":{"attributes":{"email":"user@example.com","password":"invalid"}}}' -X POST \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json"
diff --git a/doc/api/v1/users/list_users.md b/doc/api/v1/users/list_users.md index 856fb01..681f70a 100644 --- a/doc/api/v1/users/list_users.md +++ b/doc/api/v1/users/list_users.md @@ -9,7 +9,7 @@
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTczfQ.Abxi19K-az8G3cuhq_ti18PBbtKAc_f3RUJuHVlXc1Q
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzQxfQ.UAxwLgNxG7xMZJrAG9LJpVG9vXEFJf1-3S4jstLtDHk
#### Route @@ -17,14 +17,14 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO #### Query Parameters -
{"data":{"type":"user_requests","attributes":{"full_name":null,"email":null,"password":null}}}: 
+
{}: 
#### cURL
curl -g "http://localhost:5000/v1/users" -X GET \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTczfQ.Abxi19K-az8G3cuhq_ti18PBbtKAc_f3RUJuHVlXc1Q"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzQxfQ.UAxwLgNxG7xMZJrAG9LJpVG9vXEFJf1-3S4jstLtDHk" ### Response @@ -41,35 +41,35 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO
{
   "data": [
     {
-      "id": "170",
+      "id": "341",
       "type": "users",
       "attributes": {
-        "email": "user5@example.com",
-        "full_name": "Danial Gibson"
+        "email": "john.smith@example.com",
+        "full_name": "John Smith"
       }
     },
     {
-      "id": "171",
+      "id": "342",
       "type": "users",
       "attributes": {
-        "email": "user6@example.com",
-        "full_name": "Yukiko Ledner"
+        "email": "michael.jordan@example.com",
+        "full_name": "Michael Jordan"
       }
     },
     {
-      "id": "172",
+      "id": "343",
       "type": "users",
       "attributes": {
-        "email": "user7@example.com",
-        "full_name": "Elois Kiehn"
+        "email": "brad.pitt@example.com",
+        "full_name": "Brad Pitt"
       }
     },
     {
-      "id": "173",
+      "id": "344",
       "type": "users",
       "attributes": {
-        "email": "user8@example.com",
-        "full_name": "Mr. Kelly Connelly"
+        "email": "steve.jobs@example.com",
+        "full_name": "Steve Jobs"
       }
     }
   ]
diff --git a/doc/api/v1/users/retrive_user.md b/doc/api/v1/users/retrive_user.md
index 3148649..729fd2a 100644
--- a/doc/api/v1/users/retrive_user.md
+++ b/doc/api/v1/users/retrive_user.md
@@ -16,22 +16,22 @@
 
 
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTc1fQ.DSxpLhyzDiN4nlSW2ikMxIZyIcBUA4au93HT18Ka9WA
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzUwfQ.QVqJIXINRS95t45J2u4MtRlGsTW_HN32G7rWZi4Wk3I
#### Route -
GET /v1/users/174
+
GET /v1/users/349
#### Query Parameters -
{"data":{"type":"user_requests","attributes":{"full_name":null,"email":null,"password":null}}}: 
+
{}: 
#### cURL -
curl -g "http://localhost:5000/v1/users/174" -X GET \
+
curl -g "http://localhost:5000/v1/users/349" -X GET \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTc1fQ.DSxpLhyzDiN4nlSW2ikMxIZyIcBUA4au93HT18Ka9WA"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzUwfQ.QVqJIXINRS95t45J2u4MtRlGsTW_HN32G7rWZi4Wk3I"
### Response @@ -47,11 +47,11 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO
{
   "data": {
-    "id": "174",
+    "id": "349",
     "type": "users",
     "attributes": {
-      "email": "user9@example.com",
-      "full_name": "Bryon Gutkowski"
+      "email": "john.smith@example.com",
+      "full_name": "John Smith"
     }
   }
 }
diff --git a/doc/api/v1/users/retrive_user_with_invalid_id.md b/doc/api/v1/users/retrive_user_with_invalid_id.md index ab13045..e4f18bc 100644 --- a/doc/api/v1/users/retrive_user_with_invalid_id.md +++ b/doc/api/v1/users/retrive_user_with_invalid_id.md @@ -16,7 +16,7 @@
Content-Type: application/vnd.api+json
 Accept: application/vnd.api+json
-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTc2fQ.SM3fZ97H8I_cvj-xHR18FmfdvXYK_XMtObYmAs4k1lQ
+Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzUyfQ.6muVDVhyVMtYqWrGs0T4wMMQVnCr-lyWCbnCGjDHh8M #### Route @@ -24,14 +24,14 @@ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExO #### Query Parameters -
{"data":{"type":"user_requests","attributes":{"full_name":null,"email":null,"password":null}}}: 
+
{}: 
#### cURL
curl -g "http://localhost:5000/v1/users/0" -X GET \
 	-H "Content-Type: application/vnd.api+json" \
 	-H "Accept: application/vnd.api+json" \
-	-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzY3NjExODgsInN1YiI6MTc2fQ.SM3fZ97H8I_cvj-xHR18FmfdvXYK_XMtObYmAs4k1lQ"
+ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzgxMjk4MDMsInN1YiI6MzUyfQ.6muVDVhyVMtYqWrGs0T4wMMQVnCr-lyWCbnCGjDHh8M" ### Response diff --git a/spec/api/v1/profiles_spec.rb b/spec/api/v1/profiles_spec.rb index 91f6719..c7159da 100644 --- a/spec/api/v1/profiles_spec.rb +++ b/spec/api/v1/profiles_spec.rb @@ -1,67 +1,122 @@ require "rails_helper" -class ProfileRequest < ActiveModelSerializers::Model - attributes :id, :full_name, :email, :password -end - -class ProfileRequestSerializer < ApplicationSerializer - attributes :full_name, :email, :password -end - resource "Profiles" do - include_context "with JSON API Headers" include_context "with JSON API Authorization header" - include_context "with JSON API post body from request class" + + let(:current_user) { create :user, email: "john.smith@example.com", full_name: "John Smith" } get "/v1/profile" do - let(:request_class) { "profile_request" } + let(:expected_data) do + { + "data" => { + "id" => current_user.id.to_s, + "type" => "users", + "attributes" => { + "email" => "john.smith@example.com", + "full_name" => "John Smith" + } + } + } + end + + it_behaves_like "API endpoint with authorization" example "Retrive Profile" do do_request expect(response_status).to eq(200) - expect(response_body).to match_response_schema("v1/users") + expect(json_response_body).to eq(expected_data) end end patch "/v1/profile" do - parameter :full_name, "full name" - parameter :email, "email" - parameter :password, "password" + with_options scope: %i[data attributes] do + parameter :full_name, "full name" + parameter :email, "email" + parameter :password, "password" + end - let(:full_name) { "Example User Updated" } + let(:id) { current_user.id } + let(:type) { "users" } + let(:full_name) { "Updated Name" } let(:email) { "user_updated@example.com" } let(:password) { "new_password" } - let(:request_class) { "profile_request" } + + let(:expected_data) do + { + "data" => { + "id" => current_user.id.to_s, + "type" => "users", + "attributes" => { + "email" => "user_updated@example.com", + "full_name" => "Updated Name" + } + } + } + end + + it_behaves_like "API endpoint with authorization" example "Update Profile" do do_request expect(response_status).to eq(200) - expect(response_body).to match_response_schema("v1/user") + expect(json_response_body).to eq(expected_data) end context "with invalid data" do let(:password) { "" } let(:email) { "invalid" } + let(:expected_data) do + { + "errors" => [ + { + "detail" => "is too short (minimum is 6 characters)", + "source" => { + "pointer" => "/data/attributes/password" + } + }, + { + "detail" => "is invalid", + "source" => { + "pointer" => "/data/attributes/email" + } + } + ] + } + end + example "Update Profile with empty password and invalid email" do do_request expect(response_status).to eq(422) - expect(response_body).to match_response_schema("v1/errors") + expect(json_response_body).to eq(expected_data) end end end delete "/v1/profile" do - let(:request_class) { "profile_request" } + let(:expected_data) do + { + "data" => { + "id" => current_user.id.to_s, + "type" => "users", + "attributes" => { + "email" => "john.smith@example.com", + "full_name" => "John Smith" + } + } + } + end + + it_behaves_like "API endpoint with authorization" example "Delete Profile" do do_request expect(response_status).to eq(200) - expect(response_body).to match_response_schema("v1/user") + expect(json_response_body).to eq(expected_data) end end end diff --git a/spec/api/v1/registrations_spec.rb b/spec/api/v1/registrations_spec.rb index d4239cf..9d4dcab 100644 --- a/spec/api/v1/registrations_spec.rb +++ b/spec/api/v1/registrations_spec.rb @@ -1,32 +1,38 @@ require "rails_helper" -class RegistrationRequest < ActiveModelSerializers::Model - attributes :id, :full_name, :email, :password -end - -class RegistrationRequestSerializer < ApplicationSerializer - attributes :full_name, :email, :password -end - resource "Registration" do include_context "with JSON API Headers" - include_context "with JSON API post body from request class" post "/v1/registrations" do - parameter :full_name, "full name" - parameter :email, "email", required: true - parameter :password, "password", required: true + with_options scope: %i[data attributes] do + parameter :full_name, "full name" + parameter :email, "email", required: true + parameter :password, "password", required: true + end + let(:user) { User.find_by(email: email) } let(:full_name) { "Example User" } let(:email) { "user@example.com" } let(:password) { "123456" } - let(:request_class) { "registration_request" } + + let(:expected_data) do + { + "data" => { + "id" => user.id.to_s, + "type" => "users", + "attributes" => { + "email" => "user@example.com", + "full_name" => "Example User" + } + } + } + end example "Create User" do do_request expect(response_status).to eq(201) - expect(response_body).to match_response_schema("v1/user") + expect(json_response_body).to eq(expected_data) end end end diff --git a/spec/api/v1/tokens_spec.rb b/spec/api/v1/tokens_spec.rb index 4c04439..7f3a3d5 100644 --- a/spec/api/v1/tokens_spec.rb +++ b/spec/api/v1/tokens_spec.rb @@ -1,45 +1,57 @@ require "rails_helper" -class TokenRequest < ActiveModelSerializers::Model - attributes :id, :email, :password -end - -class TokenRequestSerializer < ApplicationSerializer - attributes :email, :password -end - resource "Tokens" do include_context "with JSON API Headers" + include_context "with frozen time" post "/v1/tokens" do - parameter :email, "email", required: true - parameter :password, "password", required: true + with_options scope: %i[data attributes] do + parameter :email, "email", required: true + parameter :password, "password", required: true + end + let!(:user) { create :user, email: email, password: "123456" } let(:email) { "user@example.com" } let(:password) { "123456" } - let(:request_class) { "token_request" } - - include_context "with JSON API post body from request class" - before do - create :user, email: email, password: "123456" + let(:jwt_token) { build(:jwt_token, subject: user) } + + let(:expected_data) do + { + "data" => { + "id" => jwt_token.token, + "type" => "jwt_tokens", + "attributes" => { + "token" => jwt_token.token + } + } + } end example "Create Token" do do_request expect(response_status).to eq(201) - expect(response_body).to match_response_schema("v1/jwt_token") + expect(json_response_body).to eq(expected_data) end context "with invalid password" do let(:password) { "invalid" } + let(:expected_data) do + { + "errors" => [{ + "code" => "invalid_credentials", + "detail" => "Invalid credentials" + }] + } + end + example "Create Token with invalid password" do do_request expect(response_status).to eq(422) - expect(response_body).to match_response_schema("v1/errors") + expect(json_response_body).to eq(expected_data) end end end diff --git a/spec/api/v1/users_spec.rb b/spec/api/v1/users_spec.rb index ed8c5f7..6f0077a 100644 --- a/spec/api/v1/users_spec.rb +++ b/spec/api/v1/users_spec.rb @@ -9,47 +9,103 @@ class UserRequestSerializer < ApplicationSerializer end resource "Users" do - include_context "with JSON API Headers" include_context "with JSON API Authorization header" - include_context "with JSON API post body from request class" get "/v1/users" do - let(:request_class) { "user_request" } + let!(:current_user) { create :user, full_name: "John Smith", email: "john.smith@example.com" } + let!(:user_1) { create :user, full_name: "Michael Jordan", email: "michael.jordan@example.com" } + let!(:user_2) { create :user, full_name: "Brad Pitt", email: "brad.pitt@example.com" } + let!(:user_3) { create :user, full_name: "Steve Jobs", email: "steve.jobs@example.com" } - before do - create_list :user, 3 + let(:expected_data) do + { + "data" => [{ + "id" => current_user.id.to_s, + "type" => "users", + "attributes" => { + "email" => "john.smith@example.com", + "full_name" => "John Smith" + } + }, { + "id" => user_1.id.to_s, + "type" => "users", + "attributes" => { + "email" => "michael.jordan@example.com", + "full_name" => "Michael Jordan" + } + }, { + "id" => user_2.id.to_s, + "type" => "users", + "attributes" => { + "email" => "brad.pitt@example.com", + "full_name" => "Brad Pitt" + } + }, { + "id" => user_3.id.to_s, + "type" => "users", + "attributes" => { + "email" => "steve.jobs@example.com", + "full_name" => "Steve Jobs" + } + }] + } end + it_behaves_like "API endpoint with authorization" + example "List Users" do do_request expect(response_status).to eq(200) - expect(response_body).to match_response_schema("v1/users") + expect(json_response_body).to eq(expected_data) end end get "/v1/users/:id" do parameter :id, "user id", required: true - let(:user) { create :user } + let(:user) { create :user, full_name: "John Smith", email: "john.smith@example.com" } let(:id) { user.id } - let(:request_class) { "user_request" } + + let(:expected_data) do + { + "data" => { + "id" => user.id.to_s, + "type" => "users", + "attributes" => { + "email" => "john.smith@example.com", + "full_name" => "John Smith" + } + } + } + end + + it_behaves_like "API endpoint with authorization" example "Retrive User" do do_request expect(response_status).to eq(200) - expect(response_body).to match_response_schema("v1/user") + expect(json_response_body).to eq(expected_data) end context "with invalid id" do let(:id) { 0 } + let(:expected_data) do + { + "errors" => [{ + "code" => "record_not_found", + "detail" => "Record not found" + }] + } + end + example "Retrive User with invalid id" do do_request expect(response_status).to eq(404) - expect(response_body).to match_response_schema("v1/errors") + expect(json_response_body).to eq(expected_data) end end end diff --git a/spec/interactors/create_jwt_spec.rb b/spec/interactors/create_jwt_spec.rb index 7a7e125..33a9062 100644 --- a/spec/interactors/create_jwt_spec.rb +++ b/spec/interactors/create_jwt_spec.rb @@ -1,25 +1,37 @@ require "rails_helper" describe CreateJwt do - let(:interactor) { described_class.new(user_attributes) } - let(:context) { interactor.context } + subject(:result) { described_class.call(user_attributes) } - let(:user_attributes) { attributes_for(:user).slice(:email, :password) } + let(:user_attributes) do + { + email: "john.smith@example.com", + password: "123456" + } + end + + before do + create :user, email: "john.smith@example.com", password: "123456" + end + + it "authenticates user" do + is_expected.to be_success - context "when user does not exist" do - it_behaves_like "failure interactor" + expect(result.jwt_token).to be_present end - context "when user exists" do - before do - create(:user, user_attributes) + context "when invalid credentials" do + let(:user_attributes) do + { + email: "john.smith@example.com", + password: "invalid password" + } end - it_behaves_like "success interactor" + it "does not authenticate user" do + is_expected.to be_failure - it "sets token in context" do - interactor.run - expect(context.jwt_token).to be_present + expect(result.jwt_token).to be_nil end end end diff --git a/spec/models/error_spec.rb b/spec/models/error_spec.rb index 00b15c0..601af59 100644 --- a/spec/models/error_spec.rb +++ b/spec/models/error_spec.rb @@ -8,7 +8,9 @@ .not_to raise_error end - its(:code) { is_expected.to be(:custom_error) } - its(:status) { is_expected.to be(:internal_server_error) } - its(:detail) { is_expected.to eql("Custom error message") } + it "creates error with custom_error code" do + expect(error.code).to be(:custom_error) + expect(error.status).to be(:internal_server_error) + expect(error.detail).to eql("Custom error message") + end end diff --git a/spec/models/jwt_token_spec.rb b/spec/models/jwt_token_spec.rb index b4e7d41..d6155c7 100644 --- a/spec/models/jwt_token_spec.rb +++ b/spec/models/jwt_token_spec.rb @@ -4,7 +4,7 @@ subject(:jwt_token) { described_class.new(payload: { "sub" => "1" }) } it "serializable resource" do - expect { ActiveModelSerializers::SerializableResource.new(jwt_token).to_json } + expect { ActiveModelSerializers::SerializableResource.new(jwt_token).as_json } .not_to raise_error end end diff --git a/spec/schemas/v1/definitions.json b/spec/schemas/v1/definitions.json deleted file mode 100644 index 3c0b85c..0000000 --- a/spec/schemas/v1/definitions.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "id": "file:/definitions.json#", - - "definitions": { - "user": { - "required": ["attributes"], - "properties": { - "attributes": { - "required": ["email", "full_name"], - "properties": { - "email": { "type": "string" }, - "full_name": { "type": ["string", "null"] } - } - } - } - }, - - "jwt_token": { - "required": ["attributes"], - "properties": { - "attributes": { - "required": ["token"], - "properties": { - "token": { "type": "string" } - } - } - } - } - } -} diff --git a/spec/schemas/v1/errors.json b/spec/schemas/v1/errors.json deleted file mode 100644 index 2f127dd..0000000 --- a/spec/schemas/v1/errors.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "allOf": [ - { "$ref": "file:/jsonapi.json#/definitions/failure" } - ] -} diff --git a/spec/schemas/v1/jsonapi.json b/spec/schemas/v1/jsonapi.json deleted file mode 100644 index a79d271..0000000 --- a/spec/schemas/v1/jsonapi.json +++ /dev/null @@ -1,381 +0,0 @@ -{ - "id": "file:/jsonapi.json#", - - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "JSON API Schema", - "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", - "oneOf": [ - { - "$ref": "#/definitions/success" - }, - { - "$ref": "#/definitions/failure" - }, - { - "$ref": "#/definitions/info" - } - ], - - "definitions": { - "success": { - "type": "object", - "required": [ - "data" - ], - "properties": { - "data": { - "$ref": "#/definitions/data" - }, - "included": { - "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".", - "type": "array", - "items": { - "$ref": "#/definitions/resource" - }, - "uniqueItems": true - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "links": { - "description": "Link members related to the primary data.", - "allOf": [ - { - "$ref": "#/definitions/links" - }, - { - "$ref": "#/definitions/pagination" - } - ] - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - "failure": { - "type": "object", - "required": [ - "errors" - ], - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/definitions/error" - }, - "uniqueItems": true - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - }, - "links": { - "$ref": "#/definitions/links" - } - }, - "additionalProperties": false - }, - "info": { - "type": "object", - "required": [ - "meta" - ], - "properties": { - "meta": { - "$ref": "#/definitions/meta" - }, - "links": { - "$ref": "#/definitions/links" - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - - "meta": { - "description": "Non-standard meta-information that can not be represented as an attribute or relationship.", - "type": "object", - "additionalProperties": true - }, - "data": { - "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.", - "oneOf": [ - { - "$ref": "#/definitions/resource" - }, - { - "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.", - "type": "array", - "items": { - "$ref": "#/definitions/resource" - }, - "uniqueItems": true - }, - { - "description": "null if the request is one that might correspond to a single resource, but doesn't currently.", - "type": "null" - } - ] - }, - "resource": { - "description": "\"Resource objects\" appear in a JSON API document to represent resources.", - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "relationships": { - "$ref": "#/definitions/relationships" - }, - "links": { - "$ref": "#/definitions/links" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - - "links": { - "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.", - "type": "object", - "properties": { - "self": { - "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.", - "type": "string", - "format": "uri-reference" - }, - "related": { - "$ref": "#/definitions/link" - } - }, - "additionalProperties": true - }, - "link": { - "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.", - "oneOf": [ - { - "description": "A string containing the link's URL.", - "type": "string", - "format": "uri-reference" - }, - { - "type": "object", - "required": [ - "href" - ], - "properties": { - "href": { - "description": "A string containing the link's URL.", - "type": "string", - "format": "uri-reference" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - } - ] - }, - - "attributes": { - "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.", - "type": "object", - "patternProperties": { - "^(?!relationships$|links$|id$|type$)\\w[-\\w_]*$": { - "description": "Attributes may contain any valid JSON value." - } - }, - "additionalProperties": false - }, - - "relationships": { - "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.", - "type": "object", - "patternProperties": { - "^(?!id$|type$)\\w[-\\w_]*$": { - "properties": { - "links": { - "$ref": "#/definitions/links" - }, - "data": { - "description": "Member, whose value represents \"resource linkage\".", - "oneOf": [ - { - "$ref": "#/definitions/relationshipToOne" - }, - { - "$ref": "#/definitions/relationshipToMany" - } - ] - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "anyOf": [ - {"required": ["data"]}, - {"required": ["meta"]}, - {"required": ["links"]} - ], - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "relationshipToOne": { - "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.", - "anyOf": [ - { - "$ref": "#/definitions/empty" - }, - { - "$ref": "#/definitions/linkage" - } - ] - }, - "relationshipToMany": { - "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.", - "type": "array", - "items": { - "$ref": "#/definitions/linkage" - }, - "uniqueItems": true - }, - "empty": { - "description": "Describes an empty to-one relationship.", - "type": "null" - }, - "linkage": { - "description": "The \"type\" and \"id\" to non-empty members.", - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - "pagination": { - "type": "object", - "properties": { - "first": { - "description": "The first page of data", - "oneOf": [ - { "type": "string", "format": "uri-reference" }, - { "type": "null" } - ] - }, - "last": { - "description": "The last page of data", - "oneOf": [ - { "type": "string", "format": "uri-reference" }, - { "type": "null" } - ] - }, - "prev": { - "description": "The previous page of data", - "oneOf": [ - { "type": "string", "format": "uri-reference" }, - { "type": "null" } - ] - }, - "next": { - "description": "The next page of data", - "oneOf": [ - { "type": "string", "format": "uri-reference" }, - { "type": "null" } - ] - } - } - }, - - "jsonapi": { - "description": "An object describing the server's implementation", - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - - "error": { - "type": "object", - "required": ["id", "status", "code", "title"], - "properties": { - "id": { - "description": "A unique identifier for this particular occurrence of the problem.", - "type": "string" - }, - "links": { - "$ref": "#/definitions/links" - }, - "status": { - "description": "The HTTP status code applicable to this problem, expressed as a string value.", - "type": "string" - }, - "code": { - "description": "An application-specific error code, expressed as a string value.", - "type": "string" - }, - "title": { - "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.", - "type": "string" - }, - "detail": { - "description": "A human-readable explanation specific to this occurrence of the problem.", - "type": "string" - }, - "source": { - "type": "object", - "properties": { - "pointer": { - "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].", - "type": "string" - }, - "parameter": { - "description": "A string indicating which query parameter caused the error.", - "type": "string" - } - } - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - } - } -} diff --git a/spec/schemas/v1/jwt_token.json b/spec/schemas/v1/jwt_token.json deleted file mode 100644 index 812e119..0000000 --- a/spec/schemas/v1/jwt_token.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "allOf": [ - { "$ref": "file:/jsonapi.json#/definitions/success" }, - { - "properties": { - "data": { "$ref": "file:/definitions.json#/definitions/jwt_token" } - } - } - ] -} diff --git a/spec/schemas/v1/user.json b/spec/schemas/v1/user.json deleted file mode 100644 index b1b19fe..0000000 --- a/spec/schemas/v1/user.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "allOf": [ - { "$ref": "file:/jsonapi.json#/definitions/success" }, - { - "properties": { - "data": { "$ref": "file:/definitions.json#/definitions/user" } - } - } - ] -} diff --git a/spec/schemas/v1/users.json b/spec/schemas/v1/users.json deleted file mode 100644 index 21fe9e8..0000000 --- a/spec/schemas/v1/users.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "allOf": [ - { "$ref": "file:/jsonapi.json#/definitions/success" }, - { - "properties": { - "data": { - "items": { "$ref": "file:/definitions.json#/definitions/user" } - } - } - } - ] -} diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb new file mode 100644 index 0000000..38e1dc3 --- /dev/null +++ b/spec/support/helpers.rb @@ -0,0 +1,5 @@ +Dir[Rails.root.join("spec", "support", "helpers", "**", "*.rb")].each { |f| require f } + +RSpec.configure do |config| + config.include ResponseHelpers +end diff --git a/spec/support/helpers/response_helper.rb b/spec/support/helpers/response_helper.rb new file mode 100644 index 0000000..bf766b4 --- /dev/null +++ b/spec/support/helpers/response_helper.rb @@ -0,0 +1,5 @@ +module ResponseHelpers + def json_response_body + JSON.parse(response_body) + end +end diff --git a/spec/support/json_matchers.rb b/spec/support/json_matchers.rb deleted file mode 100644 index d80a0e4..0000000 --- a/spec/support/json_matchers.rb +++ /dev/null @@ -1,3 +0,0 @@ -require "json_matchers/rspec" - -JsonMatchers.schema_root = "spec/schemas" diff --git a/spec/support/shared_contexts/json_api_headers.rb b/spec/support/shared_contexts/json_api_headers.rb index 6f1b70e..bc10049 100644 --- a/spec/support/shared_contexts/json_api_headers.rb +++ b/spec/support/shared_contexts/json_api_headers.rb @@ -1,17 +1,18 @@ shared_context "with JSON API Headers" do header "Content-Type", "application/vnd.api+json" header "Accept", "application/vnd.api+json" + + let(:raw_post) { params.to_json } end shared_context "with JSON API Authorization header" do + include_context "with JSON API Headers" + let(:current_user) { create(:user) } let(:jwt_token) { build(:jwt_token, subject: current_user) } let(:authorization) { "Bearer #{jwt_token.token}" } - header "Authorization", :authorization -end - -shared_context "with JSON API post body from request class" do - let(:request_resource) { request_class.classify.constantize.new(params) } - let(:raw_post) { ActiveModelSerializers::SerializableResource.new(request_resource).to_json } + before do + header "Authorization", :authorization + end end diff --git a/spec/support/shared_contexts/time_is_frozen.rb b/spec/support/shared_contexts/time_is_frozen.rb new file mode 100644 index 0000000..385efc2 --- /dev/null +++ b/spec/support/shared_contexts/time_is_frozen.rb @@ -0,0 +1,7 @@ +shared_context "with frozen time" do + let(:current_time) { Time.current } + + before { Timecop.freeze(current_time) } + + after { Timecop.return } +end diff --git a/spec/support/shared_examples/api_endpoint_with_authorization.rb b/spec/support/shared_examples/api_endpoint_with_authorization.rb index 4c6d59b..d70669b 100644 --- a/spec/support/shared_examples/api_endpoint_with_authorization.rb +++ b/spec/support/shared_examples/api_endpoint_with_authorization.rb @@ -1,12 +1,23 @@ -# rubocop:disable RSpec/EmptyExampleGroup shared_examples "API endpoint with authorization" do - context "without authorization headers", document: false do - header "Authorization", "" + context "without authorization headers" do + before do + header "Authorization", "" + end + + let(:expected_data) do + { + "errors" => [{ + "code" => "unauthorized", + "detail" => "Authorization required" + }] + } + end + + example "Request without authorization header", document: false do + do_request - example_request "Request without authorization header" do expect(response_status).to eq(401) - expect(response_body).to match_response_schema("error") + expect(json_response_body).to eq(expected_data) end end end -# rubocop:enable RSpec/EmptyExampleGroup From 4157c4302787aa96114dfeb17100112a2463bd2d Mon Sep 17 00:00:00 2001 From: eduardryazapov Date: Thu, 27 Sep 2018 17:12:51 +0300 Subject: [PATCH 2/2] remove interactor examples --- spec/support/shared_examples/failure_interactor.rb | 7 ------- spec/support/shared_examples/success_interactor.rb | 6 ------ 2 files changed, 13 deletions(-) delete mode 100644 spec/support/shared_examples/failure_interactor.rb delete mode 100644 spec/support/shared_examples/success_interactor.rb diff --git a/spec/support/shared_examples/failure_interactor.rb b/spec/support/shared_examples/failure_interactor.rb deleted file mode 100644 index dd5fb27..0000000 --- a/spec/support/shared_examples/failure_interactor.rb +++ /dev/null @@ -1,7 +0,0 @@ -shared_examples "failure interactor" do |params| - it "fails" do - interactor.run - expect(context).to be_failure - expect(context.message).to eql(params[:message]) if params - end -end diff --git a/spec/support/shared_examples/success_interactor.rb b/spec/support/shared_examples/success_interactor.rb deleted file mode 100644 index e4cf2b4..0000000 --- a/spec/support/shared_examples/success_interactor.rb +++ /dev/null @@ -1,6 +0,0 @@ -shared_examples "success interactor" do - it "success" do - interactor.run - expect(context).to be_success - end -end