Mojolicious-Plugin-OpenAPI-Modern
view release on metacpan or search on metacpan
t/validate_request_response.t view on Meta::CPAN
# vim: set ts=8 sts=2 sw=2 tw=100 et :
use strictures 2;
use 5.020;
use stable 0.031 'postderef';
use experimental 'signatures';
use if "$]" >= 5.022, experimental => 're_strict';
no if "$]" >= 5.031009, feature => 'indirect';
no if "$]" >= 5.033001, feature => 'multidimensional';
no if "$]" >= 5.033006, feature => 'bareword_filehandles';
no if "$]" >= 5.041009, feature => 'smartmatch';
no feature 'switch';
use open ':std', ':encoding(UTF-8)'; # force stdin, stdout, stderr into utf8
use Mojolicious::Plugin::OpenAPI::Modern;
use Path::Tiny;
use JSON::Schema::Modern::Utilities 'jsonp';
use utf8;
use lib 't/lib';
use Helper;
use Test2::Warnings 0.033 qw(:no_end_test had_no_warnings allow_patterns);
use Test::Mojo;
use Test::Memory::Cycle;
# this comes from Test::Memory::Cycle, when looking at Mojo::Routes
allow_patterns(qr{^Unhandled type: REGEXP at .*/Devel/Cycle.pm});
use lib 't/lib';
use Helper;
my $openapi_preamble = {
openapi => $::OAD_VERSION,
info => {
title => 'Test API with raw schema',
version => '1.2.3',
},
};
my $doc_uri_rel = Mojo::URL->new('/api');
subtest 'validate_request helper' => sub {
my $t = Test::Mojo->new(
'BasicApp',
{
openapi => {
document_uri => $doc_uri_rel,
schema => YAML::PP->new(boolean => 'JSON::PP')->load_string("openapi: $::OAD_VERSION\n".<<'YAML')} });
info:
title: Test API with raw schema
version: 1.2.3
components:
responses:
validation_response:
description: capture validation result
content:
application/json:
schema:
additionalProperties: false
properties:
result:
properties:
valid:
type: boolean
errors:
type: array
items:
type: object
paths:
/foo/{foo_id}:
parameters:
- name: foo_id
in: path
required: true
schema:
type: string
pattern: ^[a-z]+$
post:
operationId: operation_foo
requestBody:
content:
text/plain:
schema:
type: string
pattern: ^[a-z]+$
responses:
200:
$ref: '#/components/responses/validation_response'
400:
$ref: '#/components/responses/validation_response'
500:
description: this response code is produced via ?status=500 in the request
content:
application/json:
schema: false
/skip_validate_request:
get:
operationId: operation_skip_validate_request
responses:
200:
description: request not validated; response body not permitted
content:
text/plain:
schema:
false
YAML
$t->post_ok('/foo/hi/there')
->status_is(400, 'path_template cannot be found')
->json_is({
result => my $expected_result = {
valid => false,
errors => [
{
instanceLocation => '/request',
keywordLocation => '/paths',
absoluteKeywordLocation => $doc_uri_rel->clone->fragment('/paths')->to_string,
error => 'no match found for request POST /foo/hi/there',
},
],
},
});
memory_cycle_ok($t->app);
cmp_result(
$BasicApp::LAST_VALIDATE_REQUEST_STASH,
my $expected_stash = superhashof({
method => 'POST',
request => isa('Mojo::Message::Request'),
}),
'stash is set in validate_request',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_STASH,
$expected_stash,
'stash is set in validate_response',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_RESULT->TO_JSON,
$expected_result,
'validate_response attempts to parse the request URI again, producing the same result',
);
$t->get_ok('/foo/hi')
->status_is(400, 'wrong HTTP method')
->json_is({
result => $expected_result = {
valid => false,
errors => [
{
instanceLocation => '/request',
keywordLocation => '/paths',
absoluteKeywordLocation => $doc_uri_rel->clone->fragment('/paths')->to_string,
error => 'no match found for request GET /foo/hi',
},
],
},
});
memory_cycle_ok($t->app);
cmp_result(
$BasicApp::LAST_VALIDATE_REQUEST_STASH,
$expected_stash = superhashof({
method => 'GET',
request => isa('Mojo::Message::Request'),
}),
'stash is set in validate_request',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_STASH,
$expected_stash,
'stash is set in validate_response',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_RESULT->TO_JSON,
$expected_result,
'validate_response attempts to parse the request URI again, producing the same result',
);
$t->post_ok('/foo/123')
->status_is(400, 'path parameter will fail validation')
->json_is({
result => {
valid => false,
errors => [
{
instanceLocation => '/request/uri/path/foo_id',
keywordLocation => jsonp(qw(/paths /foo/{foo_id} parameters 0 schema pattern)),
absoluteKeywordLocation => $doc_uri_rel->clone->fragment(jsonp(qw(/paths /foo/{foo_id} parameters 0 schema pattern))),
error => 'pattern does not match',
},
],
},
});
memory_cycle_ok($t->app);
cmp_result(
$BasicApp::LAST_VALIDATE_REQUEST_STASH,
$expected_stash = superhashof({
method => 'POST',
operation_id => 'operation_foo',
path_template => '/foo/{foo_id}',
request => isa('Mojo::Message::Request'),
}),
'stash is set in validate_request',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_STASH,
$expected_stash,
'stash is set in validate_response',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_RESULT->TO_JSON,
{ valid => true },
'validate_response ran successfully',
);
$t->post_ok('/foo/hi', { 'Content-Type' => 'text/plain' }, '123')
->status_is(400, 'valid path; body does not match')
->json_is({
result => {
valid => false,
errors => [
{
instanceLocation => '/request/body/content',
keywordLocation => jsonp(qw(/paths /foo/{foo_id} post requestBody content text/plain schema pattern)),
absoluteKeywordLocation => $doc_uri_rel->clone->fragment(jsonp(qw(/paths /foo/{foo_id} post requestBody content text/plain schema pattern))),
error => 'pattern does not match',
},
],
},
});
memory_cycle_ok($t->app);
cmp_result(
$BasicApp::LAST_VALIDATE_REQUEST_STASH,
superhashof({
method => 'POST',
operation_id => 'operation_foo',
path_template => '/foo/{foo_id}',
path_captures => { foo_id => 'hi' },
request => isa('Mojo::Message::Request'),
}),
'stash is set in validate_request',
);
$t->post_ok('/foo/hi?status=500', { 'Content-Type' => 'text/plain' }, 'hi')
->status_is(500, 'custom status code')
->json_is({
result => { valid => true },
});
memory_cycle_ok($t->app);
cmp_result(
$BasicApp::LAST_VALIDATE_REQUEST_STASH,
$expected_stash = superhashof({
method => 'POST',
operation_id => 'operation_foo',
path_template => '/foo/{foo_id}',
path_captures => { foo_id => 'hi' },
request => isa('Mojo::Message::Request'),
}),
'stash is set in validate_request',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_STASH,
$expected_stash,
'stash is set in validate_response',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_RESULT->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/response/body/content',
keywordLocation => jsonp(qw(/paths /foo/{foo_id} post responses 500 content application/json schema)),
absoluteKeywordLocation => $doc_uri_rel->clone->fragment(jsonp(qw(/paths /foo/{foo_id} post responses 500 content application/json schema)))->to_string,
error => 'response body not permitted',
},
],
},
'validate_response does not like error responses',
);
$t->get_ok('/skip_validate_request')
->status_is(200)
->content_is('ok');
memory_cycle_ok($t->app);
cmp_result(
$BasicApp::LAST_VALIDATE_REQUEST_STASH,
undef,
'stash was not set in validate_request',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_STASH,
superhashof({
method => 'GET',
operation_id => 'operation_skip_validate_request',
path_template => '/skip_validate_request',
path_captures => {},
}),
'stash is set in validate_response, even though validate_request never ran',
);
cmp_result(
$BasicApp::LAST_VALIDATE_RESPONSE_RESULT->TO_JSON,
{
valid => false,
errors => [
{
instanceLocation => '/response/body/content',
keywordLocation => jsonp(qw(/paths /skip_validate_request get responses 200 content text/plain schema)),
absoluteKeywordLocation => $doc_uri_rel->clone->fragment(jsonp(qw(/paths /skip_validate_request get responses 200 content text/plain schema)))->to_string,
error => 'response body not permitted',
},
],
},
'response from this endpoint never passes the specification',
);
};
had_no_warnings() if $ENV{AUTHOR_TESTING};
done_testing;
( run in 1.497 second using v1.01-cache-2.11-cpan-39bf76dae61 )