Koha/xt/api.t
Tomas Cohen Arazi 35e9ac9b0a
Bug 37018: Add 400 response definition to all routes
This patch adds a test for well defined 400 responses on all verbs and
paths on the API spec.

The tests verify:

* Presence of 400 response definition
* The description must start with 'Bad request' (needs coding guideline)
* If DBIC queries are allowed on the route, then `invalid_query` needs
  to be mentioned in the description.

All routes get fixed to make the tests pass.

To test:
1. Apply this patch
2. Run:
   $ ktd --shell
  k$ yarn api:bundle
  k$ prove xt/api.t
=> SUCCESS: Tests pass!

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
2024-08-01 17:26:44 +02:00

172 lines
5.7 KiB
Perl
Executable file

# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Test::More tests => 5;
use Test::Mojo;
use Data::Dumper;
use FindBin();
use IPC::Cmd qw(can_run);
use List::MoreUtils qw(any);
my $t = Test::Mojo->new('Koha::REST::V1');
my $spec = $t->get_ok( '/api/v1/', 'Correctly fetched the spec' )->tx->res->json;
my $paths = $spec->{paths};
my @missing_additionalProperties = ();
foreach my $route ( keys %{$paths} ) {
foreach my $verb ( keys %{ $paths->{$route} } ) {
# p($paths->{$route}->{$verb});
# check parameters []
foreach my $parameter ( @{ $paths->{$route}->{$verb}->{parameters} } ) {
if ( exists $parameter->{schema}
&& exists $parameter->{schema}->{type}
&& ref( $parameter->{schema}->{type} ) ne 'ARRAY'
&& $parameter->{schema}->{type} eq 'object' )
{
# it is an object type definition
if (
$parameter->{name} ne 'query' # our query parameter is under-specified
and not exists $parameter->{schema}->{additionalProperties}
)
{
push @missing_additionalProperties,
{
type => 'parameter',
route => $route,
verb => $verb,
name => $parameter->{name}
};
}
}
}
# check responses {}
my $responses = $paths->{$route}->{$verb}->{responses};
foreach my $response ( keys %{$responses} ) {
if ( exists $responses->{$response}->{schema}
&& exists $responses->{$response}->{schema}->{type}
&& ref( $responses->{$response}->{schema}->{type} ) ne 'ARRAY'
&& $responses->{$response}->{schema}->{type} eq 'object' )
{
# it is an object type definition
if ( not exists $responses->{$response}->{schema}->{additionalProperties} ) {
push @missing_additionalProperties,
{
type => 'response',
route => $route,
verb => $verb,
name => $response
};
}
}
}
}
}
is( scalar @missing_additionalProperties, 0 )
or diag Dumper \@missing_additionalProperties;
subtest 'The spec passes the swagger-cli validation' => sub {
plan tests => 1;
if ( can_run('swagger-cli') ) {
my $spec_dir = "$FindBin::Bin/../api/v1/swagger";
my $var = qx{swagger-cli validate $spec_dir/swagger.yaml 2>&1};
is( $?, 0, 'Validation exit code is 0' )
or diag $var;
} else {
ok( 0, "Test skipped, swagger-cli missing" );
}
};
subtest 'tags tests' => sub {
plan tests => 1;
my @top_level_tags = map { $_->{name} } @{ $spec->{tags} };
my @errors;
foreach my $route ( keys %{$paths} ) {
foreach my $verb ( keys %{ $paths->{$route} } ) {
my @tags = @{ $paths->{$route}->{$verb}->{tags} };
# Check tag has an entry in the top level tags section
foreach my $tag (@tags) {
push @errors, "$verb $route -> uses tag '$tag' not present in top level list"
unless any { $_ eq $tag } @top_level_tags;
}
}
}
is_deeply( \@errors, [], 'No tag errors in the spec' );
foreach my $error (@errors) {
print STDERR "$error\n";
}
};
subtest '400 response tests' => sub {
plan tests => 1;
my @errors;
foreach my $route ( sort keys %{$paths} ) {
foreach my $verb ( keys %{ $paths->{$route} } ) {
my $response_400 = $paths->{$route}->{$verb}->{responses}->{400};
if ( !$response_400 ) {
push @errors, "$verb $route -> response 400 absent";
next;
}
push @errors,
"$verb $route -> 'description' does not start with 'Bad request': ($response_400->{description})"
unless $response_400->{description} =~ /^Bad request/;
my $ref = $response_400->{schema}->{'$ref'};
push @errors, "$verb $route -> '\$ref' is not '#/definitions/error': ($ref)"
unless $ref eq '#/definitions/error';
# GET routes with q parameter must mention the `invalid_query` error code
if ( ( any { $_->{in} eq 'body' && $_->{name} eq 'query' } @{ $paths->{$route}->{$verb}->{parameters} } )
|| ( any { $_->{in} eq 'query' && $_->{name} eq 'q' } @{ $paths->{$route}->{$verb}->{parameters} } ) )
{
push @errors,
"$verb $route -> 'description' does not include '* \`invalid_query\`': ($response_400->{description})"
unless $response_400->{description} =~ /\* \`invalid_query\`/;
}
}
}
is( scalar @errors, 0, 'No errors in 400 definitions in the spec' );
foreach my $error (@errors) {
print STDERR "$error\n";
}
};