File Coverage

blib/lib/Test/RestAPI.pm
Criterion Covered Total %
statement 74 86 86.0
branch 9 18 50.0
condition n/a
subroutine 20 21 95.2
pod 3 3 100.0
total 106 128 82.8


line stmt bran cond sub pod time code
1             package Test::RestAPI;
2 9     9   8818 use Moo;
  9         81346  
  9         43  
3              
4             our $VERSION = '0.1.4';
5              
6 9     9   14164 use Types::Standard qw(ArrayRef InstanceOf Int Str);
  9         435383  
  9         85  
7 9     9   10823 use Test::RestAPI::Endpoint qw(convert_path_to_filename);
  9         25  
  9         525  
8 9     9   3280 use Test::RestAPI::MojoGenerator;
  9         23  
  9         291  
9 9     9   3900 use Port::Selector;
  9         35115  
  9         212  
10 9     9   43 use Path::Tiny;
  9         13  
  9         378  
11 9     9   425 use Mojo::JSON qw(decode_json);
  9         1971  
  9         341  
12 9     9   489 use Mojo::UserAgent;
  9         95493  
  9         108  
13              
14             BEGIN {
15 9 50   9   1788 if ($^O eq 'MSWin32') {
16             ## no critic (ProhibitStringyEval)
17 0         0 eval q{
18             use Win32::Process qw(NORMAL_PRIORITY_CLASS);
19             };
20              
21 0 0       0 die $@ if $@;
22             }
23 0         0 else {
24 9     9   385 use constant NORMAL_PRIORITY_CLASS => 'fake';
  9         14  
  9         477  
25             }
26             }
27              
28             =head1 NAME
29              
30             Test::RestAPI - Real mock of REST API
31              
32             =head1 SYNOPSIS
33              
34             my $api = Test::RestAPI->new(
35             endpoints => [
36             Test::RestAPI::Endpoint->new(
37             endpoint => '/a',
38             method => 'any',
39             )
40             ],
41             );
42              
43             $api->start();
44              
45             HTTP::Tiny->new->get($api->uri.'/test');
46              
47              
48             =head1 DESCRIPTION
49              
50             In many (test) case you need mock some REST API. One way is mock your REST-API class abstraction or HTTP client.
51             This module provides other way - start generated L server and provides pseudo-real your defined API.
52              
53             =head1 METHODS
54              
55             =head2 new(%attribute)
56              
57             =head3 %attribute
58              
59             =head4 endpoints
60              
61             I of instances L
62              
63             default is I (root) 200 OK - hello:
64              
65             Test::RestAPI::Endpoint->new(
66             endpoint => '/',
67             method => 'any',
68             body => 'Hello',
69             );
70              
71             =cut
72             has 'endpoints' => (
73             is => 'ro',
74             isa => ArrayRef [ InstanceOf ['Test::RestAPI::Endpoint'] ],
75             default => sub {
76             return [
77             Test::RestAPI::Endpoint->new(
78             path => '/',
79             method => 'any',
80             render => {text => 'Hello'},
81             )
82             ];
83             }
84             );
85              
86             =head4 mojo_app_generator
87              
88             This attribute is used for generating mojo application.
89              
90             default is L
91              
92             =cut
93             has 'mojo_app_generator' => (
94             is => 'ro',
95             isa => InstanceOf ['Test::RestAPI::MojoGenerator'],
96             default => sub {
97             return Test::RestAPI::MojoGenerator->new();
98             }
99             );
100              
101             has 'pid' => (
102             is => 'rw',
103             isa => Int,
104             );
105              
106             has 'uri' => (
107             is => 'rw',
108             isa => Str,
109             );
110              
111             has 'mojo_home' => (
112             is => 'ro',
113             default => sub {
114             my $mojo_home = Path::Tiny->tempdir();
115              
116             path($mojo_home, 'log')->mkpath();
117              
118             return $mojo_home;
119             }
120             );
121              
122             =head3 start
123              
124             Start REST API (L) application on some random unused port
125             and wait to initialize.
126              
127             For start new process is used C on non-windows machines and L for windows machines.
128              
129             For generating L application is used L in C attribute - is possible set own generator.
130              
131             =cut
132             sub start {
133 8     8 1 328 my ($self) = @_;
134              
135 8         88 my $app_path = $self->mojo_app_generator->create_app($self->endpoints);
136              
137 9     9   46 use feature qw(say);
  9         9  
  9         5529  
138              
139 8         36 $self->pid($self->_start($app_path));
140             }
141              
142             sub _start {
143 8     8   12 my ($self, $app_path) = @_;
144              
145 8         24 $self->_create_uri();
146              
147 8 50       4476 if ($^O eq 'MSWin32') {
148 0         0 return $self->_start_win($app_path);
149             }
150              
151 8         30 my $pid =$self->_start_fork($app_path);
152              
153 4         91 $self->_wait_to_start();
154              
155 4         21 return $pid;
156             }
157              
158             sub _create_uri {
159 8     8   14 my ($self) = @_;
160              
161 8         104 my $port = Port::Selector->new->port();
162              
163 8         5536 $self->uri("http://localhost:$port");
164             }
165              
166             sub _start_win {
167 0     0   0 my ($self, $app_path) = @_;
168              
169 0         0 require Win32::Process;
170 0         0 Win32::Process->import();
171              
172 0         0 my $args = 'perl '.$app_path->stringify.' '.$self->_mojo_args();
173              
174 0 0       0 Win32::Process::Create(
175             my $proc,
176             $^X,
177             $args,
178             0,
179             NORMAL_PRIORITY_CLASS,
180             "."
181             ) || die "Process $args start fail $^E";
182              
183 0         0 return $proc->GetProcessID();
184             }
185              
186             sub _start_fork {
187 8     8   10 my ($self, $app_path) = @_;
188              
189 8         38 my @args = ($^X, $app_path->stringify, $self->_mojo_args());
190              
191 8         8879 my $pid = fork;
192              
193 8 100       435 if ($pid) {
    50          
194 4         225 return $pid
195             }
196             elsif ($pid == 0) {
197 4         43 exec {$args[0]} @args;
  4         0  
198 0         0 exit 1;
199             }
200             else {
201 0         0 die "Fork problem: $!";
202             }
203             }
204              
205             sub _mojo_args {
206 8     8   36 my ($self) = @_;
207              
208 8         124 return ('daemon', '-l', $self->uri, '-m', 'production', '--home', $self->mojo_home->stringify);
209             }
210              
211             sub _wait_to_start {
212 4     4   19 my ($self) = @_;
213              
214 4         20 while (1) {
215 10 100       6001324 if (Mojo::UserAgent->new->get($self->uri.'/app_mojo_healtcheck')->res->body() eq 'OK') {
216 4         32252 return 1;
217             }
218 6         25815 sleep 1;
219             }
220             }
221              
222             =head2 count_of_requests($path)
223              
224             return count of request to C<$path> endpoint
225              
226             =cut
227             sub count_of_requests {
228 2     2 1 11696 my ($self, $path) = @_;
229              
230 2 50       17 $path = '/' if !defined $path;
231              
232 2         34 my $fh = path($self->mojo_home, convert_path_to_filename($path))->filehandle();
233              
234 2         389 my $lines = 0;
235 2         44 while (<$fh>) {
236 3         55 $lines++;
237             }
238              
239 2         28 return $lines;
240             }
241              
242             =head2 list_of_requests_body($path)
243              
244             return list (ArrayRef) of requests body to C<$path> endpoint
245              
246             =cut
247             sub list_of_requests_body {
248 1     1 1 9800 my ($self, $path) = @_;
249              
250 1 50       10 $path = '/' if !defined $path;
251              
252 1         12 my $fh = path($self->mojo_home, convert_path_to_filename($path))->filehandle();
253              
254 1         138 my @lines;
255 1         15 while (my $line = <$fh>) {
256 2         68 chomp $line;
257              
258 2         9 push @lines, decode_json($line);
259             }
260              
261 1         52 return \@lines;
262             }
263              
264              
265             sub DESTROY {
266 4     4   17183 my ($self) = @_;
267              
268 4         101 kill 15, $self->pid;
269             }
270              
271             =head1 LICENSE
272              
273             Copyright (C) Avast Software.
274              
275             This library is free software; you can redistribute it and/or modify
276             it under the same terms as Perl itself.
277              
278             =head1 AUTHOR
279              
280             Jan Seidl Eseidl@avast.comE
281              
282             =cut
283              
284             1;