line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package PawsX::FakeImplementation::Instance { |
2
|
4
|
|
|
4
|
|
639553
|
use Moose; |
|
4
|
|
|
|
|
36
|
|
3
|
4
|
|
|
4
|
|
33608
|
use Paws; |
|
4
|
|
|
|
|
754082
|
|
|
4
|
|
|
|
|
133
|
|
4
|
4
|
|
|
4
|
|
4602
|
use UUID qw/uuid/; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
with 'Paws::Net::CallerRole'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub caller_to_response {} |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
has api_class => ( |
13
|
|
|
|
|
|
|
is => 'ro', |
14
|
|
|
|
|
|
|
isa => 'Str', |
15
|
|
|
|
|
|
|
required => 1, |
16
|
|
|
|
|
|
|
); |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has params => ( |
19
|
|
|
|
|
|
|
is => 'ro', |
20
|
|
|
|
|
|
|
isa => 'HashRef', |
21
|
|
|
|
|
|
|
default => sub { {} }, |
22
|
|
|
|
|
|
|
); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
has instance => ( |
25
|
|
|
|
|
|
|
is => 'ro', |
26
|
|
|
|
|
|
|
lazy => 1, |
27
|
|
|
|
|
|
|
default => sub { |
28
|
|
|
|
|
|
|
my $self = shift; |
29
|
|
|
|
|
|
|
Paws->load_class($self->api_class); |
30
|
|
|
|
|
|
|
return $self->api_class->new(%{ $self->params }); |
31
|
|
|
|
|
|
|
} |
32
|
|
|
|
|
|
|
); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sub do_call { |
35
|
|
|
|
|
|
|
my ($self, $service, $call_obj) = @_; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my $uuid = uuid; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
my ($method_name) = ($call_obj->meta->name =~ m/^Paws::.*?::(.*)$/); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $return = eval { $self->instance->$method_name($call_obj) }; |
42
|
|
|
|
|
|
|
if ($@) { |
43
|
|
|
|
|
|
|
if (ref($@)) { |
44
|
|
|
|
|
|
|
if ($@->isa('Paws::Exception')){ |
45
|
|
|
|
|
|
|
$@->throw; |
46
|
|
|
|
|
|
|
} else { |
47
|
|
|
|
|
|
|
Paws::Exception->throw(message => "$@", code => 'InternalError', request_id => $uuid); |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
} else { |
50
|
|
|
|
|
|
|
Paws::Exception->throw(message => "$@", code => 'InternalError', request_id => $uuid); |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
} else { |
53
|
|
|
|
|
|
|
if (not defined $call_obj->_returns or $call_obj->_returns eq 'Paws::API::Response') { |
54
|
|
|
|
|
|
|
$return = Paws::API::Response->new(request_id => $uuid); |
55
|
|
|
|
|
|
|
} else { |
56
|
|
|
|
|
|
|
$return = $self->new_with_coercions($call_obj->_returns, %$return); |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
return $return; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub new_with_coercions { |
63
|
|
|
|
|
|
|
my ($self, $class, %params) = @_; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Paws->load_class($class); |
66
|
|
|
|
|
|
|
my %p; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
if ($class->does('Paws::API::StrToObjMapParser')) { |
69
|
|
|
|
|
|
|
my ($subtype) = ($class->meta->find_attribute_by_name('Map')->type_constraint =~ m/^HashRef\[(.*?)\]$/); |
70
|
|
|
|
|
|
|
if (my ($array_of) = ($subtype =~ m/^ArrayRef\[(.*?)\]$/)){ |
71
|
|
|
|
|
|
|
$p{ Map } = { map { $_ => [ map { $self->new_with_coercions("$array_of", %$_) } @{ $params{ $_ } } ] } keys %params }; |
72
|
|
|
|
|
|
|
} else { |
73
|
|
|
|
|
|
|
$p{ Map } = { map { $_ => $self->new_with_coercions("$subtype", %{ $params{ $_ } }) } keys %params }; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
} elsif ($class->does('Paws::API::StrToNativeMapParser')) { |
76
|
|
|
|
|
|
|
$p{ Map } = { %params }; |
77
|
|
|
|
|
|
|
} else { |
78
|
|
|
|
|
|
|
foreach my $att (keys %params){ |
79
|
|
|
|
|
|
|
my $att_meta = $class->meta->find_attribute_by_name($att); |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Moose->throw_error("$class doesn't have an $att") if (not defined $att_meta); |
82
|
|
|
|
|
|
|
my $type = $att_meta->type_constraint; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
if ($type eq 'Bool') { |
85
|
|
|
|
|
|
|
$p{ $att } = ($params{ $att } == 1)?1:0; |
86
|
|
|
|
|
|
|
} elsif ($type eq 'Str' or $type eq 'Num' or $type eq 'Int') { |
87
|
|
|
|
|
|
|
$p{ $att } = $params{ $att }; |
88
|
|
|
|
|
|
|
} elsif ($type =~ m/^ArrayRef\[(.*?)\]$/){ |
89
|
|
|
|
|
|
|
my $subtype = "$1"; |
90
|
|
|
|
|
|
|
if ($subtype eq 'Str' or $subtype eq 'Str|Undef' or $subtype eq 'Num' or $subtype eq 'Int' or $subtype eq 'Bool') { |
91
|
|
|
|
|
|
|
$p{ $att } = $params{ $att }; |
92
|
|
|
|
|
|
|
} else { |
93
|
|
|
|
|
|
|
$p{ $att } = [ map { $self->new_with_coercions("$subtype", %{ $_ }) } @{ $params{ $att } } ]; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
} elsif ($type->isa('Moose::Meta::TypeConstraint::Enum')){ |
96
|
|
|
|
|
|
|
$p{ $att } = $params{ $att }; |
97
|
|
|
|
|
|
|
} else { |
98
|
|
|
|
|
|
|
$p{ $att } = $self->new_with_coercions("$type", %{ $params{ $att } }); |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
return $class->new(%p); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
1; |
106
|
|
|
|
|
|
|
### main pod documentation begin ### |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=encoding UTF-8 |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head1 NAME |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance - A Paws extension to help you write fake AWS services |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 SYNOPSIS |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
use Paws; |
117
|
|
|
|
|
|
|
use Paws::Net::MultiplexCaller; |
118
|
|
|
|
|
|
|
use PawsX::FakeImplementation::Instance; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
my $paws = Paws->new( |
121
|
|
|
|
|
|
|
config => { |
122
|
|
|
|
|
|
|
caller => Paws::Net::MultiplexCaller->new( |
123
|
|
|
|
|
|
|
caller_for => { |
124
|
|
|
|
|
|
|
SQS => PawsX::FakeImplementation::Instance->new( |
125
|
|
|
|
|
|
|
api_class => 'FakeSQS', |
126
|
|
|
|
|
|
|
), |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
), |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
); |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
my $sqs = $paws->service('SQS', region => 'test'); |
133
|
|
|
|
|
|
|
my $new_queue = $sqs->CreateQueue(QueueName => 'MyQueue'); |
134
|
|
|
|
|
|
|
# the FakeSQS implementation has returned a $new_queue object just |
135
|
|
|
|
|
|
|
# like SQS would: |
136
|
|
|
|
|
|
|
# $new_queue->QueueUrl eq 'http://sqs.fake.amazonaws.com/123456789012/MyQueue' |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
my $qurl = $result->QueueUrl; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
my $sent_mess = $sqs->SendMessage(MessageBody => 'Message 1', QueueUrl => $new_queue->QueueUrl); |
141
|
|
|
|
|
|
|
# $sent_mess->MessageId has a unique id |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
my $rec_mess = $sqs->ReceiveMessage(QueueUrl => $qurl); |
144
|
|
|
|
|
|
|
# $rec_mess->Messages->[0]->Body eq 'Message 1' |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head1 DESCRIPTION |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
When working heavily with AWS services you will sometimes have special needs: |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=over |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item Working on a plane (or in situations with limited connectivity) |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item Testing your application |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=item Generating faults |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=back |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance will help you create fake implementations for any service you |
161
|
|
|
|
|
|
|
want. You will be able to emulate any service of your choice, and implement the behaviour you need to |
162
|
|
|
|
|
|
|
be tested. |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance teams up with Paws::Net::MultiplexCaller to route the appropiate |
165
|
|
|
|
|
|
|
service calls to the appropiate fake implementation. See L<Paws::Net::MultiplexCaller> for more info |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head1 Creating a fake implementation |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance defines an interface between the fake implementations and Paws so |
170
|
|
|
|
|
|
|
that it's easy to write these fake implementations. Here's a guide by example to do it: |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head2 Create your fake implementation class |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
We start out creating a new class for our fake service: |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
package My::Fake::SQS; |
177
|
|
|
|
|
|
|
use Moose; |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
1; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Be careful with namespacing: take into account that your fake implementation could be partial |
182
|
|
|
|
|
|
|
(not a full emulation of the service), or your fake could have specialized behaviour like: |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=over |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=item Only implementing a subset of calls |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=item Only implementing partial behaviour for some calls (feature incomplete) |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=item Implements some type of failure mode (fails one of every 10 calls to the service) |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=back |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
So please try to name your fakes accordingly: C<My::Fake::BasicSQS>, C<My::Fake::SQS::OutOfOrder>, |
195
|
|
|
|
|
|
|
C<My::FakeSQS::OnlyAdministrativeCalls>, C<My::FakeSQS::FailSomeCalls> |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
If you are going to write a generic fake, trying to closely emulate the AWS service, you can use |
198
|
|
|
|
|
|
|
the C<PawsX::FakeService::SERVICE_NAME> namespace. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Please have the behaviour of these generic fakes well tested and be willing to accept contributions |
201
|
|
|
|
|
|
|
from third parties to these fakes, as people will probably turn to those implementations by default |
202
|
|
|
|
|
|
|
to test services. Please document any already known differences between the real service and the |
203
|
|
|
|
|
|
|
fake service. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 Write a fake method |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Just create a sub named like the method you want to fake in your fake service class. It will receive |
208
|
|
|
|
|
|
|
an object with the parameters that were passed to the service: |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub CreateQueue { |
211
|
|
|
|
|
|
|
my ($self, $params) = @_; |
212
|
|
|
|
|
|
|
# $params->QueueName holds what the user passed to |
213
|
|
|
|
|
|
|
# $sqs->CreateQueue(QueueName => '...'); |
214
|
|
|
|
|
|
|
return { QueueUrl => 'http://myqueue' }; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
The $params object in this case is a C<Paws::SQS::CreateQueue> object (that represents the parameters |
218
|
|
|
|
|
|
|
to the CreateQueue call. |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=head2 Return values |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
The return of CreateQueue is a hashref that contains the attributes for inflating a C<Paws::SQS::CreateQueueResult>. |
223
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance will convert the hashref to the appropiate return object that the calling |
224
|
|
|
|
|
|
|
code is expecting. |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub CreateQueue { |
227
|
|
|
|
|
|
|
return { QueueUrl => 'http://myqueue' }; |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
from the fake implementation will be received in the calling side as always: |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
my $return = $sqs->CreateQueue(QueueName => 'x'); |
233
|
|
|
|
|
|
|
print $return->QueueUrl; # http://myqueue |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=head2 Controlled exceptions |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
If the code inside a fake implementation throws or returns a Paws::Exception, the "user code" will recieve the |
238
|
|
|
|
|
|
|
Paws::Exception just like if Paws had generated it. |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub CreateQueue { |
241
|
|
|
|
|
|
|
Paws::Exception->throw(message => 'The name is duplicate', code => 'DuplicateName'); |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
This helps emulate error conditions just like Paws/AWS returns them |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=head2 Uncontrolled exceptions |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
If the code inside a fake implementation dies, PawsX::FakeImplementation::Instance will wrap it inside a generic |
249
|
|
|
|
|
|
|
Paws::Exception object with code 'InternalError' and the exception as the message. Paws' contract with the |
250
|
|
|
|
|
|
|
outside world is to throw Paws::Exception objects in case of problems, so PawsX::FakeImplementation::Instance |
251
|
|
|
|
|
|
|
tries to not bubble non-Paws-compliant exceptions. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head2 Instance Storage |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance instances your object one time only, and after that routes method calls |
256
|
|
|
|
|
|
|
to your object. This lets you use an attribute to store data for the lifetime of your object, and use it |
257
|
|
|
|
|
|
|
as "storage". A queue service, for example could use |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
has _queue => (is => 'ro', isa => 'ArrayRef'); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
as storage for the messages it enqueues. Every call to the fake services methods will see $self->_queue, and |
262
|
|
|
|
|
|
|
be able to manipulate it: |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub ReceiveMessage { |
265
|
|
|
|
|
|
|
my ($self, $params) = @_; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
my $message = pop @{ $self->_queue }; |
268
|
|
|
|
|
|
|
return { Messages => $message }; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head2 Externally configurable attributes |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
If you want your fake service to be configurable in some way, you can specify an attribute in your |
274
|
|
|
|
|
|
|
fake service class. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
package My::Fake::FailingSQS; |
277
|
|
|
|
|
|
|
use Moose; |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
has failing_call_ratio => (is => 'ro', isa => 'Num', default => 0.5); |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub CreateQueue { |
282
|
|
|
|
|
|
|
my ($self, $params) = @_; |
283
|
|
|
|
|
|
|
die "Strange error" if (rand() < $self->failing_call_ratio); |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
1; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
The user of the fake service can then initialize it in the following way: |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
my $paws = Paws->new( |
291
|
|
|
|
|
|
|
config => { |
292
|
|
|
|
|
|
|
caller => Paws::Net::MultiplexCaller->new( |
293
|
|
|
|
|
|
|
caller_for => { |
294
|
|
|
|
|
|
|
SQS => PawsX::FakeImplementation::Instance->new( |
295
|
|
|
|
|
|
|
api_class => 'My::Fake::FailingSQS', |
296
|
|
|
|
|
|
|
params => { |
297
|
|
|
|
|
|
|
failing_call_ratio => 1 # all calls fail |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
), |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
), |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
It's recommended that your attribute either has a default (for easy usage), or declares itself as required |
306
|
|
|
|
|
|
|
as to guide the consumer of the fake service what parameters need to be passed. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=head1 AUTHOR |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Jose Luis Martinez |
311
|
|
|
|
|
|
|
CPAN ID: JLMARTIN |
312
|
|
|
|
|
|
|
CAPSiDE |
313
|
|
|
|
|
|
|
jlmartinez@capside.com |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=head1 SEE ALSO |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
L<Paws> |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
L<Paws::Net::MultiplexCaller> |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
L<https://github.com/pplu/aws-sdk-perl> |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head1 BUGS and SOURCE |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
The source code is located here: L<https://github.com/pplu/pawsx-fakeimplementation-instance> |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
Please report bugs to: L<https://github.com/pplu/pawsx-fakeimplementation-instance/issues> |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=head1 COPYRIGHT and LICENSE |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
Copyright (c) 2017 by CAPSiDE |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
This code is distributed under the Apache 2 License. The full text of the license can be found in the LICENSE file included with this module. |