sober_swag 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/config/rubocop_linter_action.yml +5 -0
- data/.github/workflows/lint.yml +15 -0
- data/.github/workflows/ruby.yml +23 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +73 -1
- data/.ruby-version +1 -1
- data/Gemfile.lock +29 -5
- data/README.md +109 -0
- data/bin/console +15 -14
- data/docs/serializers.md +203 -0
- data/example/.rspec +1 -0
- data/example/.ruby-version +1 -1
- data/example/Gemfile +10 -6
- data/example/Gemfile.lock +96 -76
- data/example/app/controllers/people_controller.rb +37 -21
- data/example/app/controllers/posts_controller.rb +102 -0
- data/example/app/models/application_record.rb +3 -0
- data/example/app/models/person.rb +6 -0
- data/example/app/models/post.rb +9 -0
- data/example/app/output_objects/person_errors_output_object.rb +5 -0
- data/example/app/output_objects/person_output_object.rb +15 -0
- data/example/app/output_objects/post_output_object.rb +10 -0
- data/example/bin/bundle +24 -20
- data/example/bin/rails +1 -1
- data/example/bin/rake +1 -1
- data/example/config/application.rb +11 -7
- data/example/config/environments/development.rb +0 -1
- data/example/config/environments/production.rb +3 -3
- data/example/config/puma.rb +5 -5
- data/example/config/routes.rb +3 -0
- data/example/config/spring.rb +4 -4
- data/example/db/migrate/20200311152021_create_people.rb +0 -1
- data/example/db/migrate/20200603172347_create_posts.rb +11 -0
- data/example/db/schema.rb +16 -7
- data/example/spec/rails_helper.rb +64 -0
- data/example/spec/requests/people/create_spec.rb +52 -0
- data/example/spec/requests/people/get_spec.rb +35 -0
- data/example/spec/requests/people/index_spec.rb +69 -0
- data/example/spec/spec_helper.rb +94 -0
- data/lib/sober_swag.rb +6 -3
- data/lib/sober_swag/compiler/error.rb +2 -0
- data/lib/sober_swag/compiler/path.rb +2 -5
- data/lib/sober_swag/compiler/paths.rb +0 -1
- data/lib/sober_swag/compiler/type.rb +28 -15
- data/lib/sober_swag/controller.rb +16 -11
- data/lib/sober_swag/controller/route.rb +18 -21
- data/lib/sober_swag/controller/undefined_body_error.rb +3 -0
- data/lib/sober_swag/controller/undefined_path_error.rb +3 -0
- data/lib/sober_swag/controller/undefined_query_error.rb +3 -0
- data/lib/sober_swag/input_object.rb +28 -0
- data/lib/sober_swag/nodes/array.rb +1 -1
- data/lib/sober_swag/nodes/base.rb +2 -4
- data/lib/sober_swag/nodes/binary.rb +2 -1
- data/lib/sober_swag/nodes/enum.rb +4 -2
- data/lib/sober_swag/nodes/list.rb +0 -1
- data/lib/sober_swag/nodes/primitive.rb +6 -5
- data/lib/sober_swag/output_object.rb +102 -0
- data/lib/sober_swag/output_object/definition.rb +30 -0
- data/lib/sober_swag/{blueprint → output_object}/field.rb +14 -4
- data/lib/sober_swag/{blueprint → output_object}/field_syntax.rb +1 -1
- data/lib/sober_swag/{blueprint → output_object}/view.rb +15 -6
- data/lib/sober_swag/parser.rb +5 -3
- data/lib/sober_swag/serializer.rb +5 -2
- data/lib/sober_swag/serializer/array.rb +12 -0
- data/lib/sober_swag/serializer/base.rb +50 -1
- data/lib/sober_swag/serializer/conditional.rb +15 -2
- data/lib/sober_swag/serializer/field_list.rb +29 -6
- data/lib/sober_swag/serializer/mapped.rb +12 -2
- data/lib/sober_swag/serializer/meta.rb +35 -0
- data/lib/sober_swag/serializer/optional.rb +17 -2
- data/lib/sober_swag/serializer/primitive.rb +4 -1
- data/lib/sober_swag/server.rb +83 -0
- data/lib/sober_swag/types.rb +3 -0
- data/lib/sober_swag/version.rb +1 -1
- data/sober_swag.gemspec +6 -4
- metadata +77 -44
- data/example/person.json +0 -4
- data/example/test/controllers/.keep +0 -0
- data/example/test/fixtures/.keep +0 -0
- data/example/test/fixtures/files/.keep +0 -0
- data/example/test/fixtures/people.yml +0 -11
- data/example/test/integration/.keep +0 -0
- data/example/test/models/.keep +0 -0
- data/example/test/models/person_test.rb +0 -7
- data/example/test/test_helper.rb +0 -13
- data/lib/sober_swag/blueprint.rb +0 -113
- data/lib/sober_swag/path.rb +0 -8
- data/lib/sober_swag/path/integer.rb +0 -21
- data/lib/sober_swag/path/lit.rb +0 -41
- data/lib/sober_swag/path/literal.rb +0 -29
- data/lib/sober_swag/path/param.rb +0 -33
@@ -0,0 +1,15 @@
|
|
1
|
+
PersonOutputObject = SoberSwag::OutputObject.define do
|
2
|
+
identifier 'Person'
|
3
|
+
field :id, primitive(:Integer).meta(description: 'Unique ID')
|
4
|
+
field :first_name, primitive(:String).meta(description: <<~MARKDOWN)
|
5
|
+
This is the first name of a person.
|
6
|
+
Note that you can't use this as a unique identifier, and you really should understand how names work before using this.
|
7
|
+
[Falsehoods programmers believe about names](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/)
|
8
|
+
is a good thing to read!
|
9
|
+
MARKDOWN
|
10
|
+
field :last_name, primitive(:String)
|
11
|
+
|
12
|
+
view :detail do
|
13
|
+
field :posts, -> { PostOutputObject.array }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
PostOutputObject = SoberSwag::OutputObject.define do
|
2
|
+
identifier 'Post'
|
3
|
+
field :id, primitive(:Integer)
|
4
|
+
field :title, primitive(:String)
|
5
|
+
field :body, primitive(:String)
|
6
|
+
|
7
|
+
view :detail do
|
8
|
+
field :person, -> { PersonOutputObject.view(:base) }
|
9
|
+
end
|
10
|
+
end
|
data/example/bin/bundle
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# this file is here to facilitate running it.
|
9
9
|
#
|
10
10
|
|
11
|
-
require
|
11
|
+
require 'rubygems'
|
12
12
|
|
13
13
|
m = Module.new do
|
14
14
|
module_function
|
@@ -18,36 +18,36 @@ m = Module.new do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def env_var_version
|
21
|
-
ENV[
|
21
|
+
ENV['BUNDLER_VERSION']
|
22
22
|
end
|
23
23
|
|
24
24
|
def cli_arg_version
|
25
25
|
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
-
return unless
|
26
|
+
return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update`
|
27
|
+
|
27
28
|
bundler_version = nil
|
28
29
|
update_index = nil
|
29
30
|
ARGV.each_with_index do |a, i|
|
30
|
-
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
31
|
-
bundler_version = a
|
32
|
-
end
|
31
|
+
bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
33
32
|
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
-
|
33
|
+
|
34
|
+
bundler_version = Regexp.last_match(1)
|
35
35
|
update_index = i
|
36
36
|
end
|
37
37
|
bundler_version
|
38
38
|
end
|
39
39
|
|
40
40
|
def gemfile
|
41
|
-
gemfile = ENV[
|
41
|
+
gemfile = ENV['BUNDLE_GEMFILE']
|
42
42
|
return gemfile if gemfile && !gemfile.empty?
|
43
43
|
|
44
|
-
File.expand_path(
|
44
|
+
File.expand_path('../Gemfile', __dir__)
|
45
45
|
end
|
46
46
|
|
47
47
|
def lockfile
|
48
48
|
lockfile =
|
49
49
|
case File.basename(gemfile)
|
50
|
-
when
|
50
|
+
when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile)
|
51
51
|
else "#{gemfile}.lock"
|
52
52
|
end
|
53
53
|
File.expand_path(lockfile)
|
@@ -55,15 +55,17 @@ m = Module.new do
|
|
55
55
|
|
56
56
|
def lockfile_version
|
57
57
|
return unless File.file?(lockfile)
|
58
|
+
|
58
59
|
lockfile_contents = File.read(lockfile)
|
59
60
|
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
61
|
+
|
60
62
|
Regexp.last_match(1)
|
61
63
|
end
|
62
64
|
|
63
65
|
def bundler_version
|
64
66
|
@bundler_version ||=
|
65
67
|
env_var_version || cli_arg_version ||
|
66
|
-
|
68
|
+
lockfile_version
|
67
69
|
end
|
68
70
|
|
69
71
|
def bundler_requirement
|
@@ -73,28 +75,32 @@ m = Module.new do
|
|
73
75
|
|
74
76
|
requirement = bundler_gem_version.approximate_recommendation
|
75
77
|
|
76
|
-
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new(
|
78
|
+
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0')
|
77
79
|
|
78
|
-
requirement +=
|
80
|
+
requirement += '.a' if bundler_gem_version.prerelease?
|
79
81
|
|
80
82
|
requirement
|
81
83
|
end
|
82
84
|
|
83
85
|
def load_bundler!
|
84
|
-
ENV[
|
86
|
+
ENV['BUNDLE_GEMFILE'] ||= gemfile
|
85
87
|
|
86
88
|
activate_bundler
|
87
89
|
end
|
88
90
|
|
89
91
|
def activate_bundler
|
90
92
|
gem_error = activation_error_handling do
|
91
|
-
gem
|
93
|
+
gem 'bundler', bundler_requirement
|
92
94
|
end
|
93
95
|
return if gem_error.nil?
|
96
|
+
|
94
97
|
require_error = activation_error_handling do
|
95
|
-
require
|
98
|
+
require 'bundler/version'
|
99
|
+
end
|
100
|
+
if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
101
|
+
return
|
96
102
|
end
|
97
|
-
|
103
|
+
|
98
104
|
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
99
105
|
exit 42
|
100
106
|
end
|
@@ -109,6 +115,4 @@ end
|
|
109
115
|
|
110
116
|
m.load_bundler!
|
111
117
|
|
112
|
-
if m.invoked_as_script?
|
113
|
-
load Gem.bin_path("bundler", "bundle")
|
114
|
-
end
|
118
|
+
load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script?
|
data/example/bin/rails
CHANGED
data/example/bin/rake
CHANGED
@@ -1,29 +1,33 @@
|
|
1
1
|
require_relative 'boot'
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'rails'
|
4
4
|
# Pick the frameworks you want:
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
5
|
+
require 'active_model/railtie'
|
6
|
+
require 'active_job/railtie'
|
7
|
+
require 'active_record/railtie'
|
8
8
|
# require "active_storage/engine"
|
9
|
-
require
|
9
|
+
require 'action_controller/railtie'
|
10
10
|
# require "action_mailer/railtie"
|
11
11
|
# require "action_mailbox/engine"
|
12
12
|
# require "action_text/engine"
|
13
|
-
require
|
13
|
+
require 'action_view/railtie'
|
14
14
|
# require "action_cable/engine"
|
15
15
|
# require "sprockets/railtie"
|
16
|
-
require
|
16
|
+
require 'rails/test_unit/railtie'
|
17
17
|
|
18
18
|
# Require the gems listed in Gemfile, including any gems
|
19
19
|
# you've limited to :test, :development, or :production.
|
20
20
|
Bundler.require(*Rails.groups)
|
21
21
|
|
22
22
|
module Example
|
23
|
+
##
|
24
|
+
# Toplevel rails app.
|
23
25
|
class Application < Rails::Application
|
24
26
|
# Initialize configuration defaults for originally generated Rails version.
|
25
27
|
config.load_defaults 6.0
|
26
28
|
|
29
|
+
config.autoload_paths << 'app/blueprints'
|
30
|
+
|
27
31
|
# Settings in config/environments/* take precedence over those specified here.
|
28
32
|
# Application configuration can go into files in config/initializers
|
29
33
|
# -- all .rb files in that directory are automatically loaded after loading
|
@@ -11,7 +11,7 @@ Rails.application.configure do
|
|
11
11
|
config.eager_load = true
|
12
12
|
|
13
13
|
# Full error reports are disabled and caching is turned on.
|
14
|
-
config.consider_all_requests_local
|
14
|
+
config.consider_all_requests_local = false
|
15
15
|
|
16
16
|
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
17
17
|
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
@@ -36,7 +36,7 @@ Rails.application.configure do
|
|
36
36
|
config.log_level = :debug
|
37
37
|
|
38
38
|
# Prepend all log lines with the following tags.
|
39
|
-
config.log_tags = [
|
39
|
+
config.log_tags = [:request_id]
|
40
40
|
|
41
41
|
# Use a different cache store in production.
|
42
42
|
# config.cache_store = :mem_cache_store
|
@@ -59,7 +59,7 @@ Rails.application.configure do
|
|
59
59
|
# require 'syslog/logger'
|
60
60
|
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
|
61
61
|
|
62
|
-
if ENV[
|
62
|
+
if ENV['RAILS_LOG_TO_STDOUT'].present?
|
63
63
|
logger = ActiveSupport::Logger.new(STDOUT)
|
64
64
|
logger.formatter = config.log_formatter
|
65
65
|
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
data/example/config/puma.rb
CHANGED
@@ -4,20 +4,20 @@
|
|
4
4
|
# the maximum value specified for Puma. Default is set to 5 threads for minimum
|
5
5
|
# and maximum; this matches the default thread size of Active Record.
|
6
6
|
#
|
7
|
-
max_threads_count = ENV.fetch(
|
8
|
-
min_threads_count = ENV.fetch(
|
7
|
+
max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
|
8
|
+
min_threads_count = ENV.fetch('RAILS_MIN_THREADS', max_threads_count)
|
9
9
|
threads min_threads_count, max_threads_count
|
10
10
|
|
11
11
|
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
12
12
|
#
|
13
|
-
port ENV.fetch(
|
13
|
+
port ENV.fetch('PORT', 3000)
|
14
14
|
|
15
15
|
# Specifies the `environment` that Puma will run in.
|
16
16
|
#
|
17
|
-
environment ENV.fetch(
|
17
|
+
environment ENV.fetch('RAILS_ENV') { 'development' }
|
18
18
|
|
19
19
|
# Specifies the `pidfile` that Puma will use.
|
20
|
-
pidfile ENV.fetch(
|
20
|
+
pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' }
|
21
21
|
|
22
22
|
# Specifies the number of `workers` to boot in clustered mode.
|
23
23
|
# Workers are forked web server processes. If using threads and workers together
|
data/example/config/routes.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
+
resources :posts
|
2
3
|
resources :people do
|
3
4
|
get :swagger, on: :collection
|
4
5
|
end
|
6
|
+
|
7
|
+
mount SoberSwag::Server.new(cache: -> { Rails.env.production? }), at: '/swagger'
|
5
8
|
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
|
6
9
|
end
|
data/example/config/spring.rb
CHANGED
data/example/db/schema.rb
CHANGED
@@ -10,14 +10,23 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 20_200_603_172_347) do
|
14
|
+
create_table 'people', force: :cascade do |t|
|
15
|
+
t.text 'first_name', null: false
|
16
|
+
t.text 'last_name', null: false
|
17
|
+
t.datetime 'date_of_birth'
|
18
|
+
t.datetime 'created_at', precision: 6, null: false
|
19
|
+
t.datetime 'updated_at', precision: 6, null: false
|
20
|
+
end
|
14
21
|
|
15
|
-
create_table
|
16
|
-
t.
|
17
|
-
t.text
|
18
|
-
t.
|
19
|
-
t.datetime
|
20
|
-
t.datetime
|
22
|
+
create_table 'posts', force: :cascade do |t|
|
23
|
+
t.integer 'person_id', null: false
|
24
|
+
t.text 'title', null: false
|
25
|
+
t.text 'body', null: false
|
26
|
+
t.datetime 'created_at', precision: 6, null: false
|
27
|
+
t.datetime 'updated_at', precision: 6, null: false
|
28
|
+
t.index ['person_id'], name: 'index_posts_on_person_id'
|
21
29
|
end
|
22
30
|
|
31
|
+
add_foreign_key 'posts', 'people'
|
23
32
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
|
+
require 'spec_helper'
|
3
|
+
ENV['RAILS_ENV'] ||= 'test'
|
4
|
+
require File.expand_path('../config/environment', __dir__)
|
5
|
+
# Prevent database truncation if the environment is production
|
6
|
+
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
7
|
+
require 'rspec/rails'
|
8
|
+
# Add additional requires below this line. Rails is not loaded until this point!
|
9
|
+
|
10
|
+
# Requires supporting ruby files with custom matchers and macros, etc, in
|
11
|
+
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
12
|
+
# run as spec files by default. This means that files in spec/support that end
|
13
|
+
# in _spec.rb will both be required and run as specs, causing the specs to be
|
14
|
+
# run twice. It is recommended that you do not name files matching this glob to
|
15
|
+
# end with _spec.rb. You can configure this pattern with the --pattern
|
16
|
+
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
|
17
|
+
#
|
18
|
+
# The following line is provided for convenience purposes. It has the downside
|
19
|
+
# of increasing the boot-up time by auto-requiring all files in the support
|
20
|
+
# directory. Alternatively, in the individual `*_spec.rb` files, manually
|
21
|
+
# require only the support files necessary.
|
22
|
+
#
|
23
|
+
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
|
24
|
+
|
25
|
+
# Checks for pending migrations and applies them before tests are run.
|
26
|
+
# If you are not using ActiveRecord, you can remove these lines.
|
27
|
+
begin
|
28
|
+
ActiveRecord::Migration.maintain_test_schema!
|
29
|
+
rescue ActiveRecord::PendingMigrationError => e
|
30
|
+
puts e.to_s.strip
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
RSpec.configure do |config|
|
34
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
35
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
36
|
+
|
37
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
38
|
+
# examples within a transaction, remove the following line or assign false
|
39
|
+
# instead of true.
|
40
|
+
config.use_transactional_fixtures = true
|
41
|
+
|
42
|
+
# You can uncomment this line to turn off ActiveRecord support entirely.
|
43
|
+
# config.use_active_record = false
|
44
|
+
|
45
|
+
# RSpec Rails can automatically mix in different behaviours to your tests
|
46
|
+
# based on their file location, for example enabling you to call `get` and
|
47
|
+
# `post` in specs under `spec/controllers`.
|
48
|
+
#
|
49
|
+
# You can disable this behaviour by removing the line below, and instead
|
50
|
+
# explicitly tag your specs with their type, e.g.:
|
51
|
+
#
|
52
|
+
# RSpec.describe UsersController, type: :controller do
|
53
|
+
# # ...
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# The different available types are documented in the features, such as in
|
57
|
+
# https://relishapp.com/rspec/rspec-rails/docs
|
58
|
+
config.infer_spec_type_from_file_location!
|
59
|
+
|
60
|
+
# Filter lines from Rails gems in backtraces.
|
61
|
+
config.filter_rails_from_backtrace!
|
62
|
+
# arbitrary gems may also be filtered via:
|
63
|
+
# config.filter_gems_from_backtrace("gem name")
|
64
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'people controller create', type: :request do
|
4
|
+
let(:request) { post '/people', params: params }
|
5
|
+
|
6
|
+
context 'with good params' do
|
7
|
+
let(:params) { { person: { first_name: 'Anthony', last_name: 'Guy' } } }
|
8
|
+
|
9
|
+
describe 'the effects of the request' do
|
10
|
+
subject { proc { request } }
|
11
|
+
|
12
|
+
it { should change(Person, :count).by(1) }
|
13
|
+
it { should change(Person.where(first_name: 'Anthony'), :count).by(1) }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'the response' do
|
17
|
+
it 'is successful' do
|
18
|
+
request
|
19
|
+
expect(response).to be_successful
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns the person' do
|
23
|
+
request
|
24
|
+
expect(JSON.parse(response.body)).to include('first_name' => 'Anthony')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with bad params' do
|
30
|
+
let(:params) { { person: { first_name: '', last_name: '' } } }
|
31
|
+
|
32
|
+
describe 'the response' do
|
33
|
+
subject { request && response }
|
34
|
+
|
35
|
+
it { should_not be_successful }
|
36
|
+
it { should_not be_server_error }
|
37
|
+
it { should be_unprocessable }
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'the act of requesting' do
|
41
|
+
subject { proc { request } }
|
42
|
+
|
43
|
+
it { should_not change(Person, :count) }
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'the response body' do
|
47
|
+
subject { request && response && JSON.parse(response.body) }
|
48
|
+
|
49
|
+
it { should have_key('first_name') }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|