File Coverage

blib/lib/Test/Mojo/Role/OpenAPI/Modern.pm
Criterion Covered Total %
statement 120 136 88.2
branch 17 26 65.3
condition 15 29 51.7
subroutine 26 30 86.6
pod 11 11 100.0
total 189 232 81.4


line stmt bran cond sub pod time code
1 3     3   13989635 use strict;
  3         12  
  3         176  
2 3     3   22 use warnings;
  3         7  
  3         407  
3             package Test::Mojo::Role::OpenAPI::Modern; # git description: v0.010-3-ge7925fd
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Test::Mojo role providing access to an OpenAPI document and parser
6             # KEYWORDS: validation evaluation JSON Schema OpenAPI Swagger HTTP request response
7              
8             our $VERSION = '0.011';
9              
10 3     3   78 use 5.020; # for fc, unicode_strings features
  3         15  
11 3     3   23 use strictures 2;
  3         34  
  3         221  
12 3     3   1821 use utf8;
  3         45  
  3         61  
13 3     3   272 use if "$]" >= 5.022, experimental => 're_strict';
  3         9  
  3         142  
14 3     3   340 no if "$]" >= 5.031009, feature => 'indirect';
  3         7  
  3         784  
15 3     3   28 no if "$]" >= 5.033001, feature => 'multidimensional';
  3         8  
  3         272  
16 3     3   21 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  3         6  
  3         232  
17 3     3   22 no if "$]" >= 5.041009, feature => 'smartmatch';
  3         7  
  3         189  
18 3     3   20 no feature 'switch';
  3         7  
  3         153  
19 3     3   21 use JSON::Schema::Modern 0.577;
  3         102  
  3         146  
20 3     3   59 use OpenAPI::Modern 0.054;
  3         81  
  3         130  
21 3     3   20 use List::Util 'any';
  3         6  
  3         469  
22 3     3   28 use namespace::clean;
  3         7  
  3         51  
23              
24 3     3   1116 use Mojo::Base -role, -signatures;
  3         12  
  3         36  
25              
26             has openapi => sub ($self) {
27             # use the plugin's object, if the plugin is being used
28             # FIXME: we should be calling $self->app->$_call_if_can, but can() isn't behaving
29             my $openapi = eval { $self->app->openapi };
30             return $openapi if $openapi;
31              
32             # otherwise try to construct our own using provided configs
33             if (my $config = $self->app->config->{openapi}) {
34             return $config->{openapi_obj} if $config->{openapi_obj};
35              
36             if (my $args = eval {
37             +require Mojolicious::Plugin::OpenAPI::Modern;
38             Mojolicious::Plugin::OpenAPI::Modern->VERSION('0.007');
39             Mojolicious::Plugin::OpenAPI::Modern::_process_configs($config);
40             }) {
41             return OpenAPI::Modern->new($args);
42             }
43             }
44              
45             die 'openapi object or configs required';
46             };
47              
48             has test_openapi_verbose => 0;
49              
50             # keys being tracked here:
51             # - request_result
52             # - response_result
53             # - validation_options
54 56     56   120 sub _openapi_stash ($self, @data) { Mojo::Util::_stash(_openapi_stash => $self, @data) }
  56         132  
  56         144  
  56         101  
  56         261  
55              
56             after _request_ok => sub ($self, @args) {
57             $self->{_openapi_stash} = {};
58             };
59              
60 4     4 1 7930 sub request_valid ($self, $desc = 'request is valid') {
  4         10  
  4         11  
  4         8  
61 4         22 $self->test('ok', $self->request_validation_result->valid, $desc);
62 4 50 33     3091 $self->dump_request_validation_result if not $self->success and $self->test_openapi_verbose;
63 4         57 return $self;
64             }
65              
66 5     5 1 63 sub response_valid ($self, $desc = 'response is valid') {
  5         14  
  5         16  
  5         11  
67 5         29 $self->test('ok', $self->response_validation_result->valid, $desc);
68 5 50 33     7772 $self->dump_response_validation_result if not $self->success and $self->test_openapi_verbose;
69 5         67 return $self;
70             }
71              
72             # expected_error can either be the 'recommended_response' or any error string in the result.
73 6     6 1 5264 sub request_not_valid ($self, $expected_error = undef, $desc = undef) {
  6         14  
  6         20  
  6         12  
  6         13  
74 6         25 my $result = $self->request_validation_result;
75 6 100 66     214 if ($expected_error and not $result->valid) {
76 5   50     131 $desc //= 'request validation error matches';
77 5 100       166 if ($expected_error eq $result->recommended_response->[1]) {
78 3         495 $self->test('pass', $desc);
79             }
80             else {
81 2     4   257 $self->test('ok', (any { $expected_error eq $_ } $result->errors), $desc);
  4         156  
82             }
83             }
84             else {
85 1   50     6 $self->test('ok', !$self->request_validation_result->valid, $desc // 'request is not valid');
86             }
87              
88 6 50 33     4224 $self->dump_request_validation_result if not $self->success and $self->test_openapi_verbose;
89 6         71 return $self;
90             }
91              
92             # expected_error must match an error string in the result.
93 3     3 1 11 sub response_not_valid ($self, $expected_error = undef, $desc = undef) {
  3         7  
  3         9  
  3         5  
  3         6  
94 3         25 my $result = $self->response_validation_result;
95 3 100 66     76 if ($expected_error and not $result->valid) {
96 2   100 3   54 $self->test('ok', (any { $expected_error eq $_ } $result->errors),
  3         106  
97             $desc // 'response validation error matches');
98             }
99             else {
100 1   50     4 $self->test('ok', !$self->response_validation_result->valid, $desc // 'response is not valid');
101             }
102              
103 3 50 33     3798 $self->dump_response_validation_result if not $self->success and $self->test_openapi_verbose;
104 3         33 return $self;
105             }
106              
107             # I'm not sure yet which names are best, why not go for both?
108 0     0 1 0 sub request_invalid { goto \&request_not_valid }
109 0     0 1 0 sub response_invalid { goto \&response_not_valid }
110              
111 20     20 1 17066 sub request_validation_result ($self) {
  20         42  
  20         40  
112 20         71 my $result = $self->_openapi_stash('request_result');
113 20 100       512 return $result if defined $result;
114              
115 7         38 $result = $self->openapi->validate_request($self->tx->req, my $options = {});
116 7         101906 $self->_openapi_stash(request_result => $result, validation_options => $options);
117 7         312 return $result;
118             }
119              
120 11     11 1 4313 sub response_validation_result ($self) {
  11         25  
  11         19  
121 11         71 my $result = $self->_openapi_stash('response_result');
122 11 100       243 return $result if defined $result;
123              
124 7         49 my $options = $self->_openapi_stash('validation_options');
125 7 100       75 $self->_openapi_stash(validation_options => $options = {}) if not $options;
126 7         98 $options->{request} = $self->tx->req;
127              
128 7         139 $result = $self->openapi->validate_response($self->tx->res, $options);
129 7         50169 $self->_openapi_stash(response_result => $result);
130 7         308 return $result;
131             }
132              
133 3     3 1 48 sub operation_id_is ($self, $operation_id, $desc = undef) {
  3         9  
  3         9  
  3         6  
  3         6  
134 3         12 my $validation_options = $self->_openapi_stash('validation_options');
135             return $self->test('fail', 'operation_id was captured during the last validation')
136 3 50       41 if not $validation_options->{operation_id};
137              
138 3   66     29 $self->test('is', $validation_options->{operation_id}, $operation_id, $desc // 'operation_id is '.$operation_id);
139             }
140              
141             my $encoder = JSON::Schema::Modern::_JSON_BACKEND()->new
142             ->allow_nonref(1)
143             ->utf8(0)
144             ->canonical(1)
145             ->pretty(1)
146             ->indent_length(2);
147              
148 0     0 1   sub dump_request_validation_result ($self, $style = 'data_only') {
  0            
  0            
  0            
149 0           my $result = $self->request_validation_result;
150 0 0         my $method = $ENV{AUTOMATED_TESTING} ? 'diag' : 'note';
151 0           $self->handler->($method, "got request validation result:\n".$encoder->encode($result->format($style)));
152             }
153              
154 0     0 1   sub dump_response_validation_result ($self, $style = 'data_only') {
  0            
  0            
  0            
155 0           my $result = $self->response_validation_result;
156 0 0         my $method = $ENV{AUTOMATED_TESTING} ? 'diag' : 'note';
157 0           $self->handler->($method, "got response validation result:\n".$encoder->encode($result->format($style)));
158             }
159              
160             1;
161              
162             __END__