Skip to content

Commit

Permalink
feat: add API.test to support testing endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
adamcooke committed Feb 18, 2021
1 parent 87f6b6b commit 410fd9c
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 72 deletions.
13 changes: 13 additions & 0 deletions doc/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Testing controllers

You can, of course, just test your full stack by using your existing web testing framework. If you want to test endpoints directly, Rapid provides a few useful tools that you can use to faciliate this.

```ruby
response = CoreAPI::Base.test_endpoint(described_class, :create) do |req|
req.json_body[:user] = { user: 'blah' }
end

response.status # => 201
response.body # => { ... }
response.headers # => { ... }
```
26 changes: 26 additions & 0 deletions lib/rapid/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
require 'rapid/helpers'
require 'rapid/manifest_errors'
require 'rapid/object_set'
require 'rapid/errors/standard_error'
require 'rapid/mock_request'

module Rapid
class API
Expand Down Expand Up @@ -67,6 +69,30 @@ def schema(host:, namespace:)
})
end

# Execute a request for a given controller & endpoint
#
# @param controller [Rapid::Controller]
# @param endpoint_name [Symbol]
# @return [Rapid::Response]
def test_endpoint(controller, endpoint)
if endpoint.is_a?(Symbol) || endpoint.is_a?(String)
endpoint_name = endpoint
endpoint = controller.definition.endpoints[endpoint.to_sym]
if endpoint.nil?
raise Rapid::StandardError, "Invalid endpoint name '#{endpoint_name}' for '#{controller.name}'"
end
end

request = Rapid::MockRequest.empty
request.api = self
request.controller = controller
request.endpoint = endpoint

yield request if block_given?

endpoint.execute(request)
end

end

end
Expand Down
4 changes: 2 additions & 2 deletions lib/rapid/definitions/polymorph_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def matches?(value)

def cast(value, request: nil, path: [])
{
'type' => @name.to_s,
'value' => type.cast(value, request: request, path: path)
type: @name.to_s,
value: type.cast(value, request: request, path: path)
}
end

Expand Down
6 changes: 6 additions & 0 deletions lib/rapid/errors/standard_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module Rapid
class StandardError < ::StandardError
end
end
2 changes: 1 addition & 1 deletion lib/rapid/field_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def generate_hash(source, request: nil, path: [])
value = field.value(source, request: request, path: field_path)
next if value == :skip

hash[field.name.to_s] = value
hash[field.name.to_sym] = value
end
end

Expand Down
18 changes: 18 additions & 0 deletions lib/rapid/mock_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'rapid/request'

module Rapid
class MockRequest < Request

def json_body
@json_body ||= {}
end

def ip
@ip ||= '127.0.0.1'
end
attr_writer :ip

end
end
2 changes: 1 addition & 1 deletion lib/rapid/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def handle_request(env, api_path)

response = request.endpoint.execute(request)
response.rack_triplet
rescue StandardError => e
rescue ::StandardError => e
if e.is_a?(RackError) || e.is_a?(Rapid::ManifestError)
return e.triplet
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rapid/request_environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def call(*args, &block)
return unless block_given?

instance_exec(@request, @response, *args, &block)
rescue StandardError => e
rescue ::StandardError => e
raise_exception(e)
end

Expand Down
4 changes: 4 additions & 0 deletions lib/rapid/request_headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def [](key)
fetch(key)
end

def []=(key, value)
@headers[self.class.make_key(key)] = value
end

class << self

def make_key(key)
Expand Down
60 changes: 56 additions & 4 deletions spec/specs/rapid/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,62 @@
it 'should return the schema' do
api = Rapid::API.create('ExampleAPI')
schema = api.schema(host: 'api.example.com', namespace: 'v1')
expect(schema['host']).to eq 'api.example.com'
expect(schema['namespace']).to eq 'v1'
expect(schema['objects']).to be_a Array
expect(schema['api']).to eq 'ExampleAPI'
expect(schema[:host]).to eq 'api.example.com'
expect(schema[:namespace]).to eq 'v1'
expect(schema[:objects]).to be_a Array
expect(schema[:api]).to eq 'ExampleAPI'
end
end

context '.test_endpoint' do
it 'returns an error if the endpoint name is incorrect' do
api = Rapid::API.create('ExampleAPI')
controller = Rapid::Controller.create('ExampleController')
expect { api.test_endpoint(controller, :invalid) }.to raise_error(Rapid::StandardError, /invalid endpoint name/i)
end

it 'executes the endpoint' do
api = Rapid::API.create('ExampleAPI')
controller = Rapid::Controller.create('ExampleController') do
endpoint :info do
field :name, :string
action do
response.add_field :name, 'Peter'
end
end
end
response = api.test_endpoint(controller, :info)
expect(response.status).to eq 200
expect(response.body[:name]).to eq 'Peter'
end

it 'executes the endpoint through the authenticator' do
api = Rapid::API.create('ExampleAPI') do
authenticator do
potential_error 'AccessDenied' do
http_status 403
code :access_denied
end
action do
if request.headers['Authorization'] == 'Bearer test'
request.identity = true
else
raise_error 'AccessDenied'
end
end
end
end
controller = Rapid::Controller.create('ExampleController') do
endpoint :info do
field :name, :string
action do
response.add_field :name, 'Peter'
end
end
end
response = api.test_endpoint(controller, :info)
expect(response.status).to eq 403
expect(response.body[:error][:code]).to eq :access_denied
end
end
end
8 changes: 4 additions & 4 deletions spec/specs/rapid/definitions/field_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,12 @@
expect(value).to be_a Array
expect(value[0]).to be_a Hash

expect(value[0]['name']).to eq 'Adam'
expect(value[0]['age']).to eq 20
expect(value[0][:name]).to eq 'Adam'
expect(value[0][:age]).to eq 20

expect(value[1]).to be_a Hash
expect(value[1]['name']).to eq 'Michael'
expect(value[1]['age']).to eq 25
expect(value[1][:name]).to eq 'Michael'
expect(value[1][:age]).to eq 25
end
end
end
2 changes: 1 addition & 1 deletion spec/specs/rapid/endpoint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
expect(response.body[:error]).to be_a Hash
expect(response.body[:error][:code]).to eq :scope_not_granted
expect(response.body[:error][:description]).to eq 'The scope required for this endpoint has not been granted to the authenticating identity'
expect(response.body[:error][:detail]['scopes']).to eq ['example']
expect(response.body[:error][:detail][:scopes]).to eq ['example']
end
end

Expand Down
Loading

0 comments on commit 410fd9c

Please sign in to comment.