grape 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -2
- data/.rubocop_todo.yml +80 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +21 -2
- data/Gemfile +1 -6
- data/Guardfile +1 -5
- data/README.md +110 -27
- data/Rakefile +1 -1
- data/UPGRADING.md +35 -0
- data/grape.gemspec +5 -2
- data/lib/grape.rb +20 -4
- data/lib/grape/api.rb +25 -467
- data/lib/grape/api/helpers.rb +7 -0
- data/lib/grape/dsl/callbacks.rb +27 -0
- data/lib/grape/dsl/configuration.rb +27 -0
- data/lib/grape/dsl/helpers.rb +86 -0
- data/lib/grape/dsl/inside_route.rb +227 -0
- data/lib/grape/dsl/middleware.rb +33 -0
- data/lib/grape/dsl/parameters.rb +79 -0
- data/lib/grape/dsl/request_response.rb +152 -0
- data/lib/grape/dsl/routing.rb +172 -0
- data/lib/grape/dsl/validations.rb +29 -0
- data/lib/grape/endpoint.rb +6 -226
- data/lib/grape/error_formatter/base.rb +28 -0
- data/lib/grape/error_formatter/json.rb +2 -0
- data/lib/grape/error_formatter/txt.rb +2 -0
- data/lib/grape/error_formatter/xml.rb +2 -0
- data/lib/grape/exceptions/base.rb +6 -0
- data/lib/grape/exceptions/validation.rb +3 -3
- data/lib/grape/exceptions/validation_errors.rb +19 -6
- data/lib/grape/locale/en.yml +5 -3
- data/lib/grape/middleware/auth/base.rb +28 -12
- data/lib/grape/middleware/auth/dsl.rb +35 -0
- data/lib/grape/middleware/auth/strategies.rb +24 -0
- data/lib/grape/middleware/auth/strategy_info.rb +15 -0
- data/lib/grape/validations.rb +3 -92
- data/lib/grape/validations/at_least_one_of.rb +25 -0
- data/lib/grape/validations/coerce.rb +2 -2
- data/lib/grape/validations/exactly_one_of.rb +2 -2
- data/lib/grape/validations/mutual_exclusion.rb +2 -2
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +1 -1
- data/lib/grape/validations/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/helpers_spec.rb +36 -0
- data/spec/grape/api_spec.rb +72 -19
- data/spec/grape/dsl/callbacks_spec.rb +44 -0
- data/spec/grape/dsl/configuration_spec.rb +37 -0
- data/spec/grape/dsl/helpers_spec.rb +54 -0
- data/spec/grape/dsl/inside_route_spec.rb +222 -0
- data/spec/grape/dsl/middleware_spec.rb +40 -0
- data/spec/grape/dsl/parameters_spec.rb +108 -0
- data/spec/grape/dsl/request_response_spec.rb +123 -0
- data/spec/grape/dsl/routing_spec.rb +132 -0
- data/spec/grape/dsl/validations_spec.rb +55 -0
- data/spec/grape/endpoint_spec.rb +60 -11
- data/spec/grape/entity_spec.rb +9 -4
- data/spec/grape/exceptions/validation_errors_spec.rb +31 -1
- data/spec/grape/middleware/auth/base_spec.rb +34 -0
- data/spec/grape/middleware/auth/dsl_spec.rb +53 -0
- data/spec/grape/middleware/auth/strategies_spec.rb +81 -0
- data/spec/grape/middleware/error_spec.rb +33 -1
- data/spec/grape/middleware/exception_spec.rb +13 -0
- data/spec/grape/validations/at_least_one_of_spec.rb +63 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +1 -1
- data/spec/grape/validations/presence_spec.rb +159 -122
- data/spec/grape/validations/zh-CN.yml +1 -1
- data/spec/grape/validations_spec.rb +77 -15
- data/spec/spec_helper.rb +1 -0
- data/spec/support/endpoint_faker.rb +23 -0
- metadata +93 -15
- data/lib/grape/middleware/auth/basic.rb +0 -13
- data/lib/grape/middleware/auth/digest.rb +0 -13
- data/lib/grape/middleware/auth/oauth2.rb +0 -83
- data/spec/grape/middleware/auth/basic_spec.rb +0 -31
- data/spec/grape/middleware/auth/digest_spec.rb +0 -47
- data/spec/grape/middleware/auth/oauth2_spec.rb +0 -135
@@ -54,6 +54,7 @@ describe Grape::Middleware::Error do
|
|
54
54
|
|
55
55
|
it 'does not trap errors by default' do
|
56
56
|
@app ||= Rack::Builder.app do
|
57
|
+
use Spec::Support::EndpointFaker
|
57
58
|
use Grape::Middleware::Error
|
58
59
|
run ExceptionApp
|
59
60
|
end
|
@@ -63,6 +64,7 @@ describe Grape::Middleware::Error do
|
|
63
64
|
context 'with rescue_all set to true' do
|
64
65
|
it 'sets the message appropriately' do
|
65
66
|
@app ||= Rack::Builder.app do
|
67
|
+
use Spec::Support::EndpointFaker
|
66
68
|
use Grape::Middleware::Error, rescue_all: true
|
67
69
|
run ExceptionApp
|
68
70
|
end
|
@@ -72,6 +74,7 @@ describe Grape::Middleware::Error do
|
|
72
74
|
|
73
75
|
it 'defaults to a 500 status' do
|
74
76
|
@app ||= Rack::Builder.app do
|
77
|
+
use Spec::Support::EndpointFaker
|
75
78
|
use Grape::Middleware::Error, rescue_all: true
|
76
79
|
run ExceptionApp
|
77
80
|
end
|
@@ -81,6 +84,7 @@ describe Grape::Middleware::Error do
|
|
81
84
|
|
82
85
|
it 'is possible to specify a different default status code' do
|
83
86
|
@app ||= Rack::Builder.app do
|
87
|
+
use Spec::Support::EndpointFaker
|
84
88
|
use Grape::Middleware::Error, rescue_all: true, default_status: 500
|
85
89
|
run ExceptionApp
|
86
90
|
end
|
@@ -90,6 +94,7 @@ describe Grape::Middleware::Error do
|
|
90
94
|
|
91
95
|
it 'is possible to return errors in json format' do
|
92
96
|
@app ||= Rack::Builder.app do
|
97
|
+
use Spec::Support::EndpointFaker
|
93
98
|
use Grape::Middleware::Error, rescue_all: true, format: :json
|
94
99
|
run ExceptionApp
|
95
100
|
end
|
@@ -99,6 +104,7 @@ describe Grape::Middleware::Error do
|
|
99
104
|
|
100
105
|
it 'is possible to return hash errors in json format' do
|
101
106
|
@app ||= Rack::Builder.app do
|
107
|
+
use Spec::Support::EndpointFaker
|
102
108
|
use Grape::Middleware::Error, rescue_all: true, format: :json
|
103
109
|
run ErrorHashApp
|
104
110
|
end
|
@@ -109,6 +115,7 @@ describe Grape::Middleware::Error do
|
|
109
115
|
|
110
116
|
it 'is possible to return errors in jsonapi format' do
|
111
117
|
@app ||= Rack::Builder.app do
|
118
|
+
use Spec::Support::EndpointFaker
|
112
119
|
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
|
113
120
|
run ExceptionApp
|
114
121
|
end
|
@@ -118,6 +125,7 @@ describe Grape::Middleware::Error do
|
|
118
125
|
|
119
126
|
it 'is possible to return hash errors in jsonapi format' do
|
120
127
|
@app ||= Rack::Builder.app do
|
128
|
+
use Spec::Support::EndpointFaker
|
121
129
|
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
|
122
130
|
run ErrorHashApp
|
123
131
|
end
|
@@ -128,6 +136,7 @@ describe Grape::Middleware::Error do
|
|
128
136
|
|
129
137
|
it 'is possible to return errors in xml format' do
|
130
138
|
@app ||= Rack::Builder.app do
|
139
|
+
use Spec::Support::EndpointFaker
|
131
140
|
use Grape::Middleware::Error, rescue_all: true, format: :xml
|
132
141
|
run ExceptionApp
|
133
142
|
end
|
@@ -137,6 +146,7 @@ describe Grape::Middleware::Error do
|
|
137
146
|
|
138
147
|
it 'is possible to return hash errors in xml format' do
|
139
148
|
@app ||= Rack::Builder.app do
|
149
|
+
use Spec::Support::EndpointFaker
|
140
150
|
use Grape::Middleware::Error, rescue_all: true, format: :xml
|
141
151
|
run ErrorHashApp
|
142
152
|
end
|
@@ -147,6 +157,7 @@ describe Grape::Middleware::Error do
|
|
147
157
|
|
148
158
|
it 'is possible to specify a custom formatter' do
|
149
159
|
@app ||= Rack::Builder.app do
|
160
|
+
use Spec::Support::EndpointFaker
|
150
161
|
use Grape::Middleware::Error, rescue_all: true,
|
151
162
|
format: :custom,
|
152
163
|
error_formatters: {
|
@@ -162,6 +173,7 @@ describe Grape::Middleware::Error do
|
|
162
173
|
|
163
174
|
it 'does not trap regular error! codes' do
|
164
175
|
@app ||= Rack::Builder.app do
|
176
|
+
use Spec::Support::EndpointFaker
|
165
177
|
use Grape::Middleware::Error
|
166
178
|
run AccessDeniedApp
|
167
179
|
end
|
@@ -171,6 +183,7 @@ describe Grape::Middleware::Error do
|
|
171
183
|
|
172
184
|
it 'responds to custom Grape exceptions appropriately' do
|
173
185
|
@app ||= Rack::Builder.app do
|
186
|
+
use Spec::Support::EndpointFaker
|
174
187
|
use Grape::Middleware::Error, rescue_all: false
|
175
188
|
run CustomErrorApp
|
176
189
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Validations::AtLeastOneOfValidator do
|
4
|
+
describe '#validate!' do
|
5
|
+
let(:scope) do
|
6
|
+
Struct.new(:opts) do
|
7
|
+
def params(arg); end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
let(:at_least_one_of_params) { [:beer, :wine, :grapefruit] }
|
11
|
+
let(:validator) { described_class.new(at_least_one_of_params, {}, false, scope.new) }
|
12
|
+
|
13
|
+
context 'when all restricted params are present' do
|
14
|
+
let(:params) { { beer: true, wine: true, grapefruit: true } }
|
15
|
+
|
16
|
+
it 'does not raise a validation exception' do
|
17
|
+
expect(validator.validate!(params)).to eql params
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'mixed with other params' do
|
21
|
+
let(:mixed_params) { params.merge!(other: true, andanother: true) }
|
22
|
+
|
23
|
+
it 'does not raise a validation exception' do
|
24
|
+
expect(validator.validate!(mixed_params)).to eql mixed_params
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when a subset of restricted params are present' do
|
30
|
+
let(:params) { { beer: true, grapefruit: true } }
|
31
|
+
|
32
|
+
it 'does not raise a validation exception' do
|
33
|
+
expect(validator.validate!(params)).to eql params
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when params keys come as strings' do
|
38
|
+
let(:params) { { 'beer' => true, 'grapefruit' => true } }
|
39
|
+
|
40
|
+
it 'does not raise a validation exception' do
|
41
|
+
expect(validator.validate!(params)).to eql params
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when none of the restricted params is selected' do
|
46
|
+
let(:params) { { somethingelse: true } }
|
47
|
+
|
48
|
+
it 'raises a validation exception' do
|
49
|
+
expect {
|
50
|
+
validator.validate! params
|
51
|
+
}.to raise_error(Grape::Exceptions::Validation)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when exactly one of the restricted params is selected' do
|
56
|
+
let(:params) { { beer: true, somethingelse: true } }
|
57
|
+
|
58
|
+
it 'does not raise a validation exception' do
|
59
|
+
expect(validator.validate!(params)).to eql params
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -63,7 +63,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
|
|
63
63
|
context 'when exactly one of the restricted params is selected' do
|
64
64
|
let(:params) { { beer: true, somethingelse: true } }
|
65
65
|
|
66
|
-
it '
|
66
|
+
it 'does not raise a validation exception' do
|
67
67
|
expect(validator.validate!(params)).to eql params
|
68
68
|
end
|
69
69
|
end
|
@@ -1,142 +1,179 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Grape::Validations::PresenceValidator do
|
4
|
+
subject do
|
5
|
+
Class.new(Grape::API) do
|
6
|
+
format :json
|
7
|
+
end
|
8
|
+
end
|
9
|
+
def app
|
10
|
+
subject
|
11
|
+
end
|
4
12
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
default_format :json
|
9
|
-
|
10
|
-
resource :bacons do
|
11
|
-
get do
|
12
|
-
"All the bacon"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
params do
|
17
|
-
requires :id, regexp: /^[0-9]+$/
|
18
|
-
end
|
19
|
-
post do
|
20
|
-
{ ret: params[:id] }
|
21
|
-
end
|
22
|
-
|
23
|
-
params do
|
24
|
-
requires :name, :company
|
25
|
-
end
|
13
|
+
context "without validation" do
|
14
|
+
before do
|
15
|
+
subject.resource :bacons do
|
26
16
|
get do
|
27
|
-
"
|
28
|
-
end
|
29
|
-
|
30
|
-
params do
|
31
|
-
requires :user, type: Hash do
|
32
|
-
requires :first_name
|
33
|
-
requires :last_name
|
34
|
-
end
|
35
|
-
end
|
36
|
-
get '/nested' do
|
37
|
-
"Nested"
|
38
|
-
end
|
39
|
-
|
40
|
-
params do
|
41
|
-
requires :admin, type: Hash do
|
42
|
-
requires :admin_name
|
43
|
-
requires :super, type: Hash do
|
44
|
-
requires :user, type: Hash do
|
45
|
-
requires :first_name
|
46
|
-
requires :last_name
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
get '/nested_triple' do
|
52
|
-
"Nested triple"
|
17
|
+
"All the bacon"
|
53
18
|
end
|
54
19
|
end
|
55
20
|
end
|
21
|
+
it 'does not validate for any params' do
|
22
|
+
get "/bacons"
|
23
|
+
expect(last_response.status).to eq(200)
|
24
|
+
expect(last_response.body).to eq("All the bacon".to_json)
|
25
|
+
end
|
56
26
|
end
|
57
27
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
28
|
+
context "with a required regexp parameter supplied in the POST body" do
|
29
|
+
before do
|
30
|
+
subject.format :json
|
31
|
+
subject.params do
|
32
|
+
requires :id, regexp: /^[0-9]+$/
|
33
|
+
end
|
34
|
+
subject.post do
|
35
|
+
{ ret: params[:id] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
it 'validates id' do
|
39
|
+
post '/'
|
40
|
+
expect(last_response.status).to eq(400)
|
41
|
+
expect(last_response.body).to eq('{"error":"id is missing"}')
|
42
|
+
|
43
|
+
io = StringIO.new('{"id" : "a56b"}')
|
44
|
+
post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
|
45
|
+
expect(last_response.body).to eq('{"error":"id is invalid"}')
|
46
|
+
expect(last_response.status).to eq(400)
|
47
|
+
|
48
|
+
io = StringIO.new('{"id" : 56}')
|
49
|
+
post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
|
50
|
+
expect(last_response.body).to eq('{"ret":56}')
|
51
|
+
expect(last_response.status).to eq(201)
|
52
|
+
end
|
66
53
|
end
|
67
54
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
55
|
+
context "with a required non-empty string" do
|
56
|
+
before do
|
57
|
+
subject.params do
|
58
|
+
requires :email, type: String, regexp: /^\S+$/
|
59
|
+
end
|
60
|
+
subject.get do
|
61
|
+
"Hello"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
it 'requires when missing' do
|
65
|
+
get '/'
|
66
|
+
expect(last_response.status).to eq(400)
|
67
|
+
expect(last_response.body).to eq('{"error":"email is missing, email is invalid"}')
|
68
|
+
end
|
69
|
+
it 'requires when empty' do
|
70
|
+
get '/', email: ""
|
71
|
+
expect(last_response.status).to eq(400)
|
72
|
+
expect(last_response.body).to eq('{"error":"email is invalid"}')
|
73
|
+
end
|
74
|
+
it "valid when set" do
|
75
|
+
get '/', email: "bob@example.com"
|
76
|
+
expect(last_response.status).to eq(200)
|
77
|
+
expect(last_response.body).to eq("Hello".to_json)
|
78
|
+
end
|
82
79
|
end
|
83
80
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
81
|
+
context "with required parameters and no type" do
|
82
|
+
before do
|
83
|
+
subject.params do
|
84
|
+
requires :name, :company
|
85
|
+
end
|
86
|
+
subject.get do
|
87
|
+
"Hello"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
it 'validates name, company' do
|
91
|
+
get '/'
|
92
|
+
expect(last_response.status).to eq(400)
|
93
|
+
expect(last_response.body).to eq('{"error":"name is missing"}')
|
94
|
+
|
95
|
+
get '/', name: "Bob"
|
96
|
+
expect(last_response.status).to eq(400)
|
97
|
+
expect(last_response.body).to eq('{"error":"company is missing"}')
|
98
|
+
|
99
|
+
get '/', name: "Bob", company: "TestCorp"
|
100
|
+
expect(last_response.status).to eq(200)
|
101
|
+
expect(last_response.body).to eq("Hello".to_json)
|
102
|
+
end
|
96
103
|
end
|
97
104
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
105
|
+
context "with nested parameters" do
|
106
|
+
before do
|
107
|
+
subject.params do
|
108
|
+
requires :user, type: Hash do
|
109
|
+
requires :first_name
|
110
|
+
requires :last_name
|
111
|
+
end
|
112
|
+
end
|
113
|
+
subject.get '/nested' do
|
114
|
+
"Nested"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
it 'validates nested parameters' do
|
118
|
+
get '/nested'
|
119
|
+
expect(last_response.status).to eq(400)
|
120
|
+
expect(last_response.body).to eq('{"error":"user is missing, user[first_name] is missing, user[last_name] is missing"}')
|
121
|
+
|
122
|
+
get '/nested', user: { first_name: "Billy" }
|
123
|
+
expect(last_response.status).to eq(400)
|
124
|
+
expect(last_response.body).to eq('{"error":"user[last_name] is missing"}')
|
125
|
+
|
126
|
+
get '/nested', user: { first_name: "Billy", last_name: "Bob" }
|
127
|
+
expect(last_response.status).to eq(200)
|
128
|
+
expect(last_response.body).to eq("Nested".to_json)
|
129
|
+
end
|
110
130
|
end
|
111
131
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
132
|
+
context "with triply nested required parameters" do
|
133
|
+
before do
|
134
|
+
subject.params do
|
135
|
+
requires :admin, type: Hash do
|
136
|
+
requires :admin_name
|
137
|
+
requires :super, type: Hash do
|
138
|
+
requires :user, type: Hash do
|
139
|
+
requires :first_name
|
140
|
+
requires :last_name
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
subject.get '/nested_triple' do
|
146
|
+
"Nested triple"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
it 'validates triple nested parameters' do
|
150
|
+
get '/nested_triple'
|
151
|
+
expect(last_response.status).to eq(400)
|
152
|
+
expect(last_response.body).to include '{"error":"admin is missing'
|
153
|
+
|
154
|
+
get '/nested_triple', user: { first_name: "Billy" }
|
155
|
+
expect(last_response.status).to eq(400)
|
156
|
+
expect(last_response.body).to include '{"error":"admin is missing'
|
157
|
+
|
158
|
+
get '/nested_triple', admin: { super: { first_name: "Billy" } }
|
159
|
+
expect(last_response.status).to eq(400)
|
160
|
+
expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user] is missing, admin[super][user][first_name] is missing, admin[super][user][last_name] is missing"}')
|
161
|
+
|
162
|
+
get '/nested_triple', super: { user: { first_name: "Billy", last_name: "Bob" } }
|
163
|
+
expect(last_response.status).to eq(400)
|
164
|
+
expect(last_response.body).to include '{"error":"admin is missing'
|
165
|
+
|
166
|
+
get '/nested_triple', admin: { super: { user: { first_name: "Billy" } } }
|
167
|
+
expect(last_response.status).to eq(400)
|
168
|
+
expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user][last_name] is missing"}')
|
169
|
+
|
170
|
+
get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: "Billy" } } }
|
171
|
+
expect(last_response.status).to eq(400)
|
172
|
+
expect(last_response.body).to eq('{"error":"admin[super][user][last_name] is missing"}')
|
173
|
+
|
174
|
+
get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: "Billy", last_name: "Bob" } } }
|
175
|
+
expect(last_response.status).to eq(200)
|
176
|
+
expect(last_response.body).to eq("Nested triple".to_json)
|
177
|
+
end
|
140
178
|
end
|
141
|
-
|
142
179
|
end
|