File Coverage

lib/DBD/Mock/Session/GenerateFixtures.pm
Criterion Covered Total %
statement 492 507 97.0
branch 67 80 83.7
condition 23 42 54.7
subroutine 73 76 96.0
pod 4 4 100.0
total 659 709 92.9


line stmt bran cond sub pod time code
1             package DBD::Mock::Session::GenerateFixtures;
2              
3 17     17   10794768 use strict;
  17         56  
  17         623  
4 17     17   88 use warnings;
  17         33  
  17         970  
5              
6 17     17   96 use Carp 'croak';
  17         27  
  17         1139  
7 17     17   10695 use DBD::Mock;
  17         386945  
  17         86  
8              
9 17     17   662 use feature 'say';
  17         34  
  17         2234  
10              
11 17     17   9422 use Sub::Override;
  17         36228  
  17         666  
12 17     17   15786 use English qw ( -no_match_vars );
  17         50779  
  17         108  
13 17     17   6379 use File::Path qw(make_path);
  17         37  
  17         1280  
14 17     17   17495 use Cpanel::JSON::XS;
  17         56762  
  17         1404  
15 17     17   9044 use File::Slurper qw (read_text);
  17         297018  
  17         2733  
16 17     17   134 use File::Spec;
  17         85  
  17         562  
17 17     17   9791 use Readonly;
  17         75778  
  17         1220  
18 17     17   9922 use Data::Walk;
  17         21252  
  17         1184  
19 17     17   2922 use Try::Tiny;
  17         11456  
  17         115906  
20              
21             our $VERSION = 1.15;
22              
23             our $override;
24             my $JSON_OBJ = Cpanel::JSON::XS->new()->utf8->pretty();
25             my $cursor = 1;
26             my $ref_cursor = \$cursor;
27              
28             Readonly::Hash my %MOCKED_DBI_METHODS => (
29             execute => 'DBI::st::execute',
30             bind_param => 'DBI::st::bind_param',
31             fetchrow_hashref => 'DBI::st::fetchrow_hashref',
32             fetchrow_arrayref => 'DBI::st::fetchrow_arrayref',
33             fetchrow_array => 'DBI::st::fetchrow_array',
34             selectall_arrayref => 'DBI::db::selectall_arrayref',
35             selectall_hashref => 'DBI::db::selectall_hashref',
36             selectcol_arrayref => 'DBI::db::selectcol_arrayref',
37             selectrow_array => 'DBI::db::selectrow_array',
38             selectrow_arrayref => 'DBI::db::selectrow_arrayref',
39             selectrow_hashref => 'DBI::db::selectrow_hashref',
40             fetch => 'DBI::st::fetch',
41             prepare_cached => 'DBI::db::prepare_cached',
42             prepare => 'DBI::db::prepare',
43             mocked_prepare => 'DBD::Mock::db::prepare',
44             mocked_execute => 'DBD::Mock::st::execute',
45             mocked_bind_param => 'DBD::Mock::st::bind_param',
46             mocked_bind_param_in_out => 'DBD::Mock::st::bind_param_inout',
47             begin_work => 'DBI::db::begin_work',
48             commit => 'DBI::db::commit',
49             rollback => 'DBI::db::rollback',
50             bind_param_in_out => 'DBI::st::bind_param_inout',
51             );
52              
53             sub new {
54 25     25 1 5072564 my ( $class, $args_for ) = @_;
55 25         76 my $self = bless {}, $class;
56              
57 25 100       89 if ($args_for) {
58 23         123 $self->_validate_args($args_for);
59 20         92 $self->_initialize($args_for);
60             }
61             else {
62 2         9 $self->_initialize();
63             }
64              
65 21         96 return $self;
66             }
67              
68             sub _initialize {
69 22     22   42 my $self = shift;
70 22         43 my $args_for = shift;
71              
72 22         63 my %args_for = ();
73              
74 22 100       70 if ($args_for) {
75 20         38 %args_for = %{$args_for};
  20         122  
76             }
77              
78 22         185 $self->_set_fixtures_file( $args_for{file} );
79 22         68 $self->{override_flag} = 0;
80 22         245 $override = Sub::Override->new();
81 22         254 $self->{override} = $override;
82              
83 22 100       490 if ( my $dbh = $args_for{dbh} ) {
    100          
    100          
84 9         24 $self->{dbh} = $dbh;
85 9         96 $self->{bind_params} = [];
86 9         85 $self->{override_flag} = 1;
87 9         66 $self->_override_dbi_methods();
88 9         27 $self->{result} = [];
89             }
90             elsif ( my $fixtures = $args_for{data} ) {
91 3         14 $self->_process_mock_data($fixtures);
92 3         12 $self->_set_mock_dbh($fixtures);
93             }
94             elsif ( -e $self->{fixture_file} ) {
95 9         71 my $data = $JSON_OBJ->decode( read_text( $self->{fixture_file} ) );
96 9         1878 $self->_process_mock_data($data);
97 9         36 $self->_set_mock_dbh($data);
98             }
99             else {
100 1         292 croak "No mocked data is available, you can resolve this by providing the 'dbh'
101             argument to the 'new' method to generate it. Alternatively, you can pass either
102             a file or data argument to the 'new' method";
103             }
104              
105 21         76 return $self;
106             }
107              
108             sub _set_mock_dbh {
109 12     12   31 my ( $self, $data ) = @_;
110              
111 12         130 my $dbh = DBI->connect(
112             'dbi:Mock:',
113             '', '',
114             {
115             RaiseError => 1,
116             PrintError => 0
117             }
118             );
119              
120 12         9697 my $dbh_session = DBD::Mock::Session->new( $PROGRAM_NAME => @{$data} );
  12         112  
121 12         846 $self->_override_dbi_mocked_prepare( $MOCKED_DBI_METHODS{mocked_prepare} );
122 12         72 $self->_override_dbi_mocked_bind_param( $MOCKED_DBI_METHODS{mocked_bind_param} );
123 12         77 $self->_override_dbi_mocked_bind_param_in_out( $MOCKED_DBI_METHODS{mocked_bind_param_in_out} );
124 12         77 $self->_override_dbi_mocked_execute( $MOCKED_DBI_METHODS{mocked_execute} );
125              
126 12         386 $dbh->{mock_session} = $dbh_session;
127 12         384 $self->{dbh} = $dbh;
128              
129 12         36 return $self;
130             }
131              
132             sub _override_dbi_methods {
133 9     9   25 my $self = shift;
134              
135 9         60 $self->_override_dbi_execute( $MOCKED_DBI_METHODS{execute} );
136 9         54 $self->_override_dbi_bind_param( $MOCKED_DBI_METHODS{bind_param} );
137 9         53 $self->_override_dbi_fetchrow_hashref( $MOCKED_DBI_METHODS{fetchrow_hashref} );
138 9         74 $self->_override_dbi_fetchrow_arrayref( $MOCKED_DBI_METHODS{fetchrow_arrayref} );
139 9         68 $self->_override_dbi_fetchrow_array( $MOCKED_DBI_METHODS{fetchrow_array} );
140 9         69 $self->_override_dbi_selectall_arrayref( $MOCKED_DBI_METHODS{selectall_arrayref} );
141 9         47 $self->_override_dbi_selectall_hashref( $MOCKED_DBI_METHODS{selectall_hashref} );
142 9         49 $self->_override_dbi_selectcol_arrayref( $MOCKED_DBI_METHODS{selectcol_arrayref} );
143 9         44 $self->_override_dbi_selectrow_array( $MOCKED_DBI_METHODS{selectrow_array} );
144 9         43 $self->_override_dbi_selectrow_arrayref( $MOCKED_DBI_METHODS{selectrow_arrayref} );
145 9         41 $self->_override_dbi_selectrow_hashref( $MOCKED_DBI_METHODS{selectrow_hashref} );
146 9         42 $self->_override_dbi_fetch( $MOCKED_DBI_METHODS{fetch} );
147 9         45 $self->_override_dbi_prepare_cached( $MOCKED_DBI_METHODS{prepare_cached} );
148 9         98 $self->_override_dbi_prepare( $MOCKED_DBI_METHODS{prepare} );
149 9         45 $self->_override_dbi_begin_work( $MOCKED_DBI_METHODS{begin_work} );
150 9         253 $self->_override_dbi_commit( $MOCKED_DBI_METHODS{commit} );
151 9         262 $self->_override_dbi_rollback( $MOCKED_DBI_METHODS{rollback} );
152 9         315 $self->_override_bind_param_in_out( $MOCKED_DBI_METHODS{bind_param_in_out} );
153              
154 9         18 return $self;
155             }
156              
157             sub get_dbh {
158 20     20 1 83 my $self = shift;
159              
160 20         65 return $self->{dbh};
161             }
162              
163             sub get_override_object {
164 228     228 1 322 my $self = shift;
165              
166 228         1886 return $self->{override};
167             }
168              
169             sub restore_all {
170 1     1 1 652 my $self = shift;
171              
172 1         13 foreach my $key ( keys %MOCKED_DBI_METHODS ) {
173 22 100       986 next if $key =~ m/^mocked/;
174 18         41 $self->get_override_object()->restore( $MOCKED_DBI_METHODS{$key} );
175             }
176              
177 1         11 return $self;
178             }
179              
180             sub _override_dbi_execute {
181 9     9   18 my $self = shift;
182 9         43 my $dbi_execute = shift;
183              
184 9         111 my $orig_execute = \&$dbi_execute;
185              
186             $self->get_override_object()->replace(
187             $dbi_execute,
188             sub {
189 57     57   8451 my ( $sth, @args ) = @_;
190              
191 57   33     494 my $sql = $sth->{Statement} // $sth->{Database}->{Statement} // '';
      0        
192 57         185 $sql = $self->_normalize_sql($sql);
193 57         66319 my $retval = $orig_execute->( $sth, @args );
194              
195 57         4140 my $col_names;
196             try {
197             $col_names = $sth->{NAME}
198 57 100   53   3826 if $sql !~ m/^INSERT|^UPDATE|^DELETE/i;
199             }
200             catch {
201 0     0   0 my $error = $_;
202              
203             # say STDERR $error;
204 57         623 };
205              
206 57         1435 my $rows = $sth->rows();
207 57         701 my $query_data = {
208             statement => $sql,
209             bound_params => \@args,
210             col_names => $col_names,
211             };
212              
213 57         121 my $result = [];
214 57 100 66     622 if ( $sql =~ m/^INSERT|^UPDATE|^DELETE/i && $retval ) {
215 14         38 push @$result, ['rows'];
216 14         40 foreach my $row ( 1 .. $rows ) {
217 16         20 push @{$result}, [];
  16         41  
218             }
219 14         35 $query_data->{results} = $result;
220             }
221              
222             $query_data->{bound_params} = $self->{bind_params}
223             if ref $self->{bind_params}
224 57 50 33     218 && scalar @{ $self->{bind_params} } > 0;
  57         239  
225              
226 57 100 100     176 if ( $self->{bind_named_params} && scalar keys %{ $self->{bind_named_params} } ) {
  48         166  
227 3         7 my %seen = ();
228 3         21 my @order = grep { !$seen{$_}++ } ( $sql =~ m/(:\w+)/g );
  6         22  
229 3         7 $query_data->{bound_params} = [ map { $self->{bind_named_params}->{$_} } @order ];
  6         20  
230             }
231              
232             # query failed:
233 57 50       120 if ( !$retval ) {
234 0         0 $query_data->{failure} = [ 5, 'Ooops!' ];
235 0         0 $query_data->{results} = undef;
236             }
237              
238 57 100       277 push @{ $self->{result} }, $query_data
  56         142  
239             if $sql !~ m/BEGIN_WORK|COMMIT/;
240 57         176 $self->_write_to_file();
241 57         208 $self->{bind_params} = [];
242 57         175 $self->{bind_named_params} = {};
243 57         1092 $self->{sth} = $sth;
244 57         366 return $retval;
245             }
246 9         38 );
247              
248 9         462 return $self;
249             }
250              
251             sub _override_dbi_mocked_execute {
252 12     12   22 my $self = shift;
253 12         30 my $mocked_dbi_execute = shift;
254              
255 12         96 my $orig_mocked_dbi_execute = \&$mocked_dbi_execute;
256              
257             $self->get_override_object()->replace(
258             $mocked_dbi_execute,
259             sub {
260 53     53   5723 my ( $sth, @params ) = @_;
261              
262 53 100 100     211 if ( $self->{bind_named_params} && scalar keys %{ $self->{bind_named_params} } ) {
  42         188  
263 5         12 my %seen = ();
264 5   33     15 my $sql = $sth->{Statement} // $sth->{Database}->{Statement} // '';
      0        
265 5         37 my @order = grep { !$seen{$_}++ } ( $sql =~ m/(:\w+)/g );
  12         38  
266 5         10 @params = map { $self->{bind_named_params}->{$_} } @order;
  12         31  
267             }
268              
269 53         144 $self->{bind_named_params} = {};
270 53         123 $self->{bind_parmas} = [];
271 53         148 return $orig_mocked_dbi_execute->( $sth, @params );
272             }
273 12         34 );
274             }
275              
276             sub _override_dbi_mocked_bind_param {
277 12     12   34 my $self = shift;
278 12         78 my $mocked_bind_param = shift;
279              
280 12         104 my $orig_mocked_bind_param = \&$mocked_bind_param;
281              
282             $self->get_override_object()->replace(
283             $mocked_bind_param,
284             sub {
285 14     14   1365 my ( $sth, $bind, $val, $attr ) = @_;
286              
287 14 100       42 if ( $bind =~ m/^:/ ) {
288 10         19 $self->{bind_named_params}->{$bind} = $val;
289             }
290             else {
291 4         11 $self->{bind_parmas}->[ $bind - 1 ] = $val;
292             }
293              
294 14         34 return $orig_mocked_bind_param->( $sth, $bind, $val, $attr );
295             }
296 12         92 );
297              
298 12         357 return $self;
299             }
300              
301             sub _override_dbi_mocked_bind_param_in_out {
302 12     12   23 my $self = shift;
303 12         54 my $mocked_bind_param_in_out = shift;
304              
305 12         97 my $orig_mocked_bind_param = \&$mocked_bind_param_in_out;
306              
307             $self->get_override_object->replace(
308             $mocked_bind_param_in_out,
309             sub {
310 6     6   172 my ( $sth, $param_num, $val, $max_len ) = @_;
311              
312 6 100       24 if ( $param_num =~ m/^:/ ) {
313 2         6 $self->{bind_named_params}->{$param_num} = $ref_cursor;
314             }
315             else {
316 4         7 $self->{bind_parmas}->[ $param_num - 1 ] = $ref_cursor;
317             }
318              
319 6         18 return $orig_mocked_bind_param->( $sth, $param_num, $ref_cursor, $max_len );
320             }
321 12         49 );
322              
323 12         356 return $self;
324             }
325              
326             sub _override_dbi_bind_param {
327 9     9   42 my $self = shift;
328 9         30 my $bind_param = shift;
329              
330 9         70 my $orig_execute = \&$bind_param;
331              
332             $self->get_override_object()->replace(
333             $bind_param,
334             sub {
335 6     6   355 my ( $sth, $bind, $val, $attr ) = @_;
336 6 0 33     18 return if !$bind && !$val;
337 6 50       22 if ( $bind =~ m/^:/ ) {
338 6         17 $self->{bind_named_params}->{$bind} = $val;
339             }
340             else {
341 0         0 $self->{bind_parmas}->[ $bind - 1 ] = $val;
342             }
343              
344 6         43 my $retval = $orig_execute->( $sth, $bind, $val, $attr );
345 6         12 return $retval;
346             }
347 9         28 );
348              
349 9         246 return $self;
350             }
351              
352             sub _override_bind_param_in_out {
353 9     9   18 my $self = shift;
354 9         26 my $bind_param_in_out = shift;
355              
356 9         132 my $orig_bind_param_in_out = \&$bind_param_in_out;
357              
358             $self->get_override_object()->replace(
359             $bind_param_in_out,
360             sub {
361 0     0   0 my ( $sth, $bind, $val, $max_length, $attr ) = @_;
362              
363 0 0       0 if ( $bind =~ m/^:/ ) {
364 0         0 $self->{bind_named_params}->{$bind} = '<CURSOR>';
365             }
366             else {
367 0         0 $self->{bind_parmas}->[ $bind - 1 ] = '<CURSOR>';
368             }
369              
370 0         0 my $retval = $orig_bind_param_in_out->( $sth, $bind, $val, $max_length, $attr );
371 0         0 return $retval;
372             }
373 9         58 );
374              
375 9         234 return $self;
376             }
377              
378             sub _override_dbi_fetchrow_hashref {
379 9     9   20 my $self = shift;
380 9         28 my $fetchrow_hashref = shift;
381              
382 9         96 my $orig_selectrow_hashref = \&$fetchrow_hashref;
383              
384             $self->get_override_object()->replace(
385             $fetchrow_hashref,
386             sub {
387 17     17   123 my ($sth) = @_;
388              
389 17         154 my $retval = $orig_selectrow_hashref->($sth);
390 17         427 $self->{result}->[-1]->{col_names} = $sth->{NAME};
391 17 100 100     143 if ( ref $retval && !defined $self->{result}->[-1]->{results} ) {
392 2         9 my $query_results = $self->_set_hashref_response( $sth, $retval );
393 2         6 push @{ $self->{result}->[-1]->{results} }, $query_results;
  2         9  
394 2         8 $self->_write_to_file();
395             }
396              
397 17         42 return $retval;
398             }
399 9         42 );
400              
401 9         233 return $self;
402             }
403              
404             sub _override_dbi_fetchrow_arrayref {
405 9     9   26 my $self = shift;
406 9         27 my $fetchrow_arrayref = shift;
407              
408 9         64 my $orig_selectrow_arrayref = \&$fetchrow_arrayref;
409              
410             $self->get_override_object()->replace(
411             $fetchrow_arrayref,
412             sub {
413 17     17   152 my ($sth) = @_;
414              
415 17         132 my $retval = $orig_selectrow_arrayref->($sth);
416 17         497 $self->{result}->[-1]->{col_names} = $sth->{NAME};
417 17         170 my @retval = ();
418 17 100       40 if ( ref $retval ) {
419 12         12 @retval = @{$retval};
  12         24  
420 12         14 push @{ $self->{result}->[-1]->{results} }, \@retval;
  12         34  
421 12         26 $self->_write_to_file();
422             }
423              
424 17         48 return $retval;
425             }
426 9         26 );
427              
428 9         250 return $self;
429             }
430              
431             sub _override_dbi_fetchrow_array {
432 9     9   16 my $self = shift;
433 9         24 my $fetchrow_array = shift;
434              
435 9         63 my $orig_selectrow_array = \&$fetchrow_array;
436              
437             $self->get_override_object()->replace(
438             $fetchrow_array,
439             sub {
440 13     13   84 my ($sth) = @_;
441              
442 13         224 my @retval = $orig_selectrow_array->($sth);
443 13         179 $self->{result}->[-1]->{col_names} = $sth->{NAME};
444              
445 13 100       77 if ( scalar @retval ) {
446 9         10 push @{ $self->{result}->[-1]->{results} }, \@retval;
  9         27  
447 9         21 $self->_write_to_file();
448             }
449              
450 13         46 return @retval;
451             }
452 9         26 );
453              
454 9         242 return $self;
455             }
456              
457             sub _override_dbi_selectall_arrayref {
458 9     9   142 my $self = shift;
459 9         118 my $selectall_arrayref = shift;
460              
461 9         65 my $result = $self->{result};
462 9         34 my $orig_selectall_arrayref = \&$selectall_arrayref;
463              
464             $self->get_override_object()->replace(
465             $selectall_arrayref,
466             sub {
467 3     3   9070 my ( $dbh, $sql, $slice, @parmas ) = @_;
468              
469 3         47 my $retval = $orig_selectall_arrayref->( $dbh, $sql, $slice, @parmas );
470 3         35 my $data = [];
471              
472 3 50       9 if ( ref $retval ) {
473 3         10 my $col_names = $self->_get_current_record_column_names();
474              
475 3         6 foreach my $row_as_hash ( @{$retval} ) {
  3         8  
476 5         8 my $row_as_array = [];
477 5         5 foreach my $col_name ( @{$col_names} ) {
  5         6  
478 10         12 push @{$row_as_array}, $row_as_hash->{$col_name};
  10         19  
479             }
480              
481 5         5 push @{$data}, $row_as_array;
  5         9  
482             }
483 3         11 $self->{result}->[-1]->{results} = $data;
484 3         7 $self->_write_to_file();
485             }
486              
487 3         12 return $retval;
488             }
489 9         46 );
490              
491 9         357 return $self;
492             }
493              
494             sub _override_dbi_selectall_hashref {
495 9     9   30 my $self = shift;
496 9         24 my $selectall_hashref = shift;
497              
498 9         66 my $orig_selectall_hashref = \&$selectall_hashref;
499              
500             $self->get_override_object()->replace(
501             $selectall_hashref,
502             sub {
503 3     3   13161 my ( $dbh, $statement, $key_field, $attr, @bind_values ) = @_;
504              
505 3         25 my $retval = $orig_selectall_hashref->( $dbh, $statement, $key_field, $attr, @bind_values );
506              
507 3         23 my $col_names = $self->_get_current_record_column_names();
508 3         5 my $mock_data = [];
509              
510             walk sub {
511 45     45   1032 my $rows = $_;
512 45 100 100     56 if ( ref $rows && scalar keys %{$rows} == scalar @{$col_names} ) {
  12         12  
  12         27  
513 6         16 my %data = %$rows;
514 6         6 push @{$mock_data}, [ @data{ @{$col_names} } ];
  6         7  
  6         16  
515 6         12 $self->_write_to_file();
516             }
517              
518 45         54 return;
519 3         23 }, $retval;
520              
521 3         93 $self->{result}->[-1]->{results} = $mock_data;
522 3         9 return $retval;
523             }
524 9         27 );
525              
526 9         244 return $self;
527             }
528              
529             sub _override_dbi_selectcol_arrayref {
530 9     9   16 my $self = shift;
531 9         60 my $selectcol_arrayref = shift;
532              
533 9         63 my $orig_selectcol_arrayref = \&$selectcol_arrayref;
534              
535             $self->get_override_object()->replace(
536             $selectcol_arrayref,
537             sub {
538 3     3   9492 my ( $dbh, $statement, $attr, @bind_values ) = @_;
539 3         5 my $mocked_data = [];
540              
541 3         24 my $retval = $orig_selectcol_arrayref->( $dbh, $statement, $attr, @bind_values );
542 3         14 my @db_data = @{$retval};
  3         6  
543              
544 3         4 my $length = 1;
545 3 100 66     14 $length = scalar @{ $attr->{Columns} }
  2         5  
546             if $attr && ref $attr eq 'HASH';
547              
548 3         9 foreach my $row ( 0 .. $#db_data ) {
549 10         14 my $query_data = [ splice( @db_data, 0, $length ) ];
550 10 100       10 last if scalar @{$query_data} == 0;
  10         15  
551 9         6 push @{$mocked_data}, $query_data;
  9         13  
552             }
553              
554 3         12 $self->{result}->[-1]->{results} = $mocked_data;
555 3         7 $self->_write_to_file();
556 3         10 return $retval;
557             }
558 9         37 );
559              
560 9         221 return $self;
561             }
562              
563             sub _override_dbi_selectrow_array {
564 9     9   27 my $self = shift;
565 9         22 my $selectrow_array = shift;
566              
567 9         92 my $original_selectrow_array = \&$selectrow_array;
568              
569             $self->get_override_object()->replace(
570             $selectrow_array,
571             sub {
572 6     6   3365 my ( $dbh, $statement, $attr, @bind_values ) = @_;
573 6         8 my $sth;
574              
575 6 100       16 if ( !ref $statement ) {
576 4         14 $sth = $dbh->prepare($statement);
577             }
578             else {
579 2         3 $sth = $statement;
580             }
581              
582 6         498 my $sql = $sth->{Statement};
583 6         308 my @retval = $original_selectrow_array->( $dbh, $statement, $attr, @bind_values );
584              
585             my $query_data = {
586             statement => $sql,
587             bound_params => \@bind_values,
588             col_names => $sth->{NAME},
589 6         484 results => [ \@retval ],
590             };
591              
592 6         68 push @{ $self->{result} }, $query_data;
  6         13  
593              
594 6         16 $self->_write_to_file();
595 6         62 return @retval;
596             }
597 9         50 );
598              
599 9         230 return $self;
600             }
601              
602             sub _override_dbi_selectrow_arrayref {
603 9     9   14 my $self = shift;
604 9         26 my $selectrow_arrayref = shift;
605              
606 9         60 my $original_selectrow_arrayref = \&$selectrow_arrayref;
607              
608             $self->get_override_object()->replace(
609             $selectrow_arrayref,
610             sub {
611 6     6   5277 my ( $dbh, $statement, $attr, @bind_values ) = @_;
612 6         9 my $sth;
613              
614 6 100       16 if ( !ref $statement ) {
615 4         34 $sth = $dbh->prepare($statement);
616             }
617             else {
618 2         3 $sth = $statement;
619             }
620              
621 6         713 my $sql = $sth->{Statement};
622 6         83 my $retval = $original_selectrow_arrayref->( $dbh, $statement, $attr, @bind_values );
623              
624             my $query_data = {
625             statement => $sql,
626             bound_params => \@bind_values,
627             col_names => $sth->{NAME},
628 6         355 results => [$retval],
629             };
630              
631 6         98 push @{ $self->{result} }, $query_data;
  6         18  
632 6         19 $self->_write_to_file();
633              
634 6         61 return $retval;
635             }
636 9         23 );
637              
638 9         217 return $self;
639             }
640              
641             sub _override_dbi_selectrow_hashref {
642 9     9   22 my $self = shift;
643 9         35 my $selectrow_hashref = shift;
644              
645 9         62 my $original_selectrow_hashref = \&$selectrow_hashref;
646              
647             $self->get_override_object()->replace(
648             $selectrow_hashref,
649             sub {
650 6     6   5606 my ( $dbh, $statement, $attr, @bind_values ) = @_;
651 6         10 my $sth;
652              
653 6 100       17 if ( !ref $statement ) {
654 4         11 $sth = $dbh->prepare($statement);
655             }
656             else {
657 2         3 $sth = $statement;
658             }
659              
660 6         679 my $sql = $sth->{Statement};
661 6         49 my $retval = $original_selectrow_hashref->( $dbh, $statement, $attr, @bind_values );
662              
663             $self->{result}->[-1]->{results} =
664 6         151 [ $self->_set_hashref_response( $sth, $retval ) ];
665              
666 6         39 return $retval;
667             }
668 9         24 );
669              
670 9         229 return $self;
671             }
672              
673             sub _override_dbi_fetch {
674 9     9   17 my $self = shift;
675 9         40 my $fetch = shift;
676              
677 9         62 my $original_fetch = \&$fetch;
678 9         21 my $result = [];
679             $self->get_override_object()->replace(
680             $fetch,
681             sub {
682 59     59   7263 my ( $sth, @args ) = @_;
683 59         597 my $row = $original_fetch->( $sth, @args );
684 59   33     757 my $sql = $sth->{Statement} // $sth->{Database}->{Statement} // '';
      0        
685 59 100       128 if ( ref $row ) {
686 39         41 my @shallow_copy = @{$row};
  39         93  
687 39         40 push @{ $self->{result}->[-1]->{results} }, \@shallow_copy;
  39         116  
688 39         95 $self->_write_to_file();
689             }
690              
691 59         185 return $row;
692             }
693 9         23 );
694              
695 9         208 return $self;
696             }
697              
698             sub _override_dbi_prepare_cached {
699 9     9   15 my $self = shift;
700 9         22 my $prepare_cached = shift;
701              
702 9         56 my $original_prepare_cached = \&$prepare_cached;
703             $self->get_override_object()->replace(
704             $prepare_cached,
705             sub {
706 4     4   8438 my ( $dbh, $sql, $attr, $if_active ) = @_;
707              
708 4         13 $sql = $self->_normalize_sql($sql);
709 4         26 return $original_prepare_cached->( $dbh, $sql, $attr, $if_active );
710             }
711 9         24 );
712 9         290 return $self;
713             }
714              
715             sub _override_dbi_prepare {
716 9     9   29 my $self = shift;
717 9         27 my $prepare = shift;
718              
719 9         60 my $original_prepare = \&$prepare;
720             $self->get_override_object()->replace(
721             $prepare,
722             sub {
723 70     70   101199 my ( $dbh, $sql, $attr ) = @_;
724              
725 70         212 $sql = $self->_normalize_sql($sql);
726 70         406 return $original_prepare->( $dbh, $sql, $attr );
727             }
728 9         24 );
729              
730 9         233 return $self;
731             }
732              
733             sub _override_dbi_mocked_prepare {
734 12     12   40 my $self = shift;
735 12         53 my $mocked_prepare = shift;
736              
737 12         131 my $original_mocked_prepare = \&$mocked_prepare;
738             $self->get_override_object()->replace(
739             $mocked_prepare,
740             sub {
741 56     56   88525 my ( $dbh, $sql ) = @_;
742              
743 56         189 $sql = $self->_normalize_sql($sql);
744 56         211 return $original_mocked_prepare->( $dbh, $sql );
745             }
746 12         49 );
747              
748 12         535 return $self;
749             }
750              
751             sub _override_dbi_begin_work {
752 9     9   16 my $self = shift;
753 9         20 my $mocked_begin_work = shift;
754              
755 9         63 my $original_begin_work = \&$mocked_begin_work,;
756             $self->get_override_object()->replace(
757             $mocked_begin_work,
758             sub {
759 2     2   3148 my $dbh = shift;
760              
761 2         4 push @{ $self->{result} },
  2         40  
762             {
763             statement => 'BEGIN WORK',
764             col_names => undef,
765             results => [ [] ]
766             };
767 2         8 $self->_write_to_file();
768 2         27 return $original_begin_work->($dbh);
769             }
770 9         26 );
771             }
772              
773             sub _override_dbi_commit {
774 9     9   17 my $self = shift;
775 9         40 my $mocked_commit = shift;
776              
777 9         58 my $original_commit = \&$mocked_commit;
778             $self->get_override_object()->replace(
779             $mocked_commit,
780             sub {
781 2     2   125 my $dbh = shift;
782              
783 2         3 push @{ $self->{result} }, { statement => 'COMMIT', col_names => undef, results => [ [] ] };
  2         12  
784 2         6 $self->_write_to_file();
785 2         15846 return $original_commit->($dbh);
786             }
787 9         36 );
788             }
789              
790             sub _override_dbi_rollback {
791 9     9   33 my $self = shift;
792 9         31 my $mocked_rollback = shift;
793              
794 9         61 my $original_rollback = \&$mocked_rollback;
795             $self->get_override_object()->replace(
796             $mocked_rollback,
797             sub {
798 0     0   0 my $dbh = shift;
799              
800 0         0 push @{ $self->{result} },
  0         0  
801             {
802             statement => 'ROLLBACK',
803             col_names => undef,
804             results => [ [] ]
805             };
806 0         0 $self->_write_to_file();
807 0         0 return $original_rollback->($dbh);
808             }
809 9         61 );
810             }
811              
812             sub _normalize_sql {
813 187     187   298 my ( $self, $sql ) = @_;
814              
815             # 1. remove multi-linie comments /* ... */
816 187         410 $sql =~ s/\/\*.*?\*\///gs;
817              
818             # 2. remove single-line comments -- ...
819 187         274 $sql =~ s/--.*$//gm;
820              
821 187         2002 $sql =~ s/\s+/ /g;
822 187         2500 $sql =~ s/^\s+|\s+$//g;
823              
824 187         363 return $sql;
825             }
826              
827             sub _get_current_record_column_names {
828 6     6   10 my $self = shift;
829              
830 6         16 return $self->{result}->[-1]->{col_names};
831             }
832              
833             sub _process_mock_data {
834 12     12   37 my ( $self, $data ) = @_;
835              
836 12         51 while ( my ( $index, $row ) = each( @{$data} ) ) {
  60         227  
837              
838             # load in the mocked session the a global scalar ref for the cursor
839             # use the same global scalar in the override for DBD::st::bind_param_inout
840 48 100 100     122 $row->{bound_params} = [ map { defined $_ && $_ eq '<CURSOR>' ? $ref_cursor : $_ } @{ $row->{bound_params} } ];
  106         432  
  48         87  
841 48 100       126 if ( $row->{col_names} ) {
842 37         55 my $cols = delete $row->{col_names};
843 37         39 unshift @{ $row->{results} }, $cols;
  37         86  
844             }
845             }
846              
847 12         21 return $self;
848             }
849              
850             sub _set_fixtures_file {
851 22     22   41 my $self = shift;
852 22         53 my $file = shift;
853              
854 22         158 Readonly::Scalar my $FIXTURE_DIR => 'db_fixtures/';
855              
856 22 100       789 if ( defined $file ) {
857 8         44 $self->{fixture_file} = $file;
858             }
859             else {
860 14         429 my ( $volume, $directory, $test_file ) = File::Spec->splitpath($PROGRAM_NAME);
861 14         1761 make_path( $directory . $FIXTURE_DIR );
862 14         66 my $default_fixture_file = $directory . $FIXTURE_DIR . "$test_file.json";
863 14         104 $self->{fixture_file} = $default_fixture_file;
864             }
865              
866 22         44 return $self;
867             }
868              
869             sub _validate_args {
870 23     23   49 my $self = shift;
871 23         44 my $args_for = shift;
872              
873 23 100       256 croak 'arguments to new must be hashref'
874             if ref $args_for ne 'HASH';
875              
876 22         160 Readonly::Hash my %ALLOWED_KEYS => (
877             dbh => 1,
878             file => 1,
879             data => 1,
880             override => 1,
881             );
882              
883 22 100       1750 croak 'to many args to new' if scalar keys %{$args_for} > 1;
  22         249  
884              
885 21         42 foreach my $key ( keys %{$args_for} ) {
  21         65  
886             croak "Key not allowed: $key"
887 21 100       129 unless $ALLOWED_KEYS{$key};
888             }
889              
890 20         410 return $self;
891             }
892              
893             sub _write_to_file {
894 147     147   171 my $self = shift;
895              
896 147         215 my $result = $self->{result};
897 147         191 my $override_flag = $self->{override_flag};
898 147         201 my $fixture_file = $self->{fixture_file};
899              
900 147 50       244 return unless defined $result;
901 147 50       255 return unless $override_flag;
902              
903 147 50 50     286 if ( $override_flag && scalar @{$result} ) {
  147         340  
904 147         4154 my $json_data = $JSON_OBJ->encode($result);
905 147 50       1726 my $fh = IO::File->new( $fixture_file, 'w' )
906             or croak "cannot open file:$fixture_file $!\n";
907 147         125119 say $fh $json_data;
908 147 50       554 $fh->close or croak "cannot close file:$fixture_file $!\n";
909 147         33455 undef $fh;
910             }
911              
912 147         347 return $self;
913             }
914              
915             sub _set_hashref_response {
916 8     8   14 my $self = shift;
917 8         11 my $sth = shift;
918 8         11 my $retval = shift;
919              
920 8         12 my $result = [];
921 8         44 my $cols = $sth->{NAME};
922 8         131 foreach my $col ( @{$cols} ) {
  8         21  
923 16         23 push @{$result}, $retval->{$col};
  16         35  
924             }
925              
926 8         32 return $result;
927             }
928              
929             sub DESTROY {
930 5     5   240 my $self = shift;
931              
932 5         12 my $result = delete $self->{result};
933 5         12 my $override_flag = delete $self->{override_flag};
934              
935 5         19 $override = delete $self->{override};
936 5         25 my $dbh = delete $self->{dbh};
937 5         9 my $fixture_file = delete $self->{fixture_file};
938              
939 5         122 return $self;
940             }
941              
942             1;
943              
944             =head1 NAME
945              
946             DBD::Mock::Session::GenerateFixtures - A module to generate fixtures for DBD::Mock::Session
947              
948             =head1 SYNOPSIS
949              
950             # Case 1: Providing a pre-existing DBI database handle for genereting a mocked data files
951             # with the test name
952             my $mock_dumper = DBD::Mock::Session::GenerateFixtures->new({ dbh => $dbh });
953             my $real_dbh = $mock_dumper->get_dbh();
954              
955             # Case 2: Read data from the same file as current test
956             my $mock_dumper = DBD::Mock::Session::GenerateFixtures->new();
957             my $dbh = $mock_dumper->get_dbh();
958             # Your code using the mock DBD
959              
960             # Case 3: Read data from a coustom file
961             my $mock_dumper = DBD::Mock::Session::GenerateFixtures->new({ file => 'path/to/fixture.json' });
962             my $dbh = $mock_dumper->get_dbh();
963             # Your code using the mock DBD
964              
965             # Case 4: Providing an array reference containing mock data
966             my $mock_dumper = DBD::Mock::Session::GenerateFixtures->new({ data => \@mock_data });
967             my $dbh = $mock_dumper->get_dbh();
968             # Your code using the mock DBD
969              
970             # or with Rose::DB
971              
972             my $mock_dumper = DBD::Mock::Session::GenerateFixtures->new();
973              
974             my $override = Sub::Override->new();
975             my $dbh = $mock_dumper->get_dbh();
976             $dbh->{mock_start_insert_id} = 3;
977              
978             $override->replace('Rose::DB::dbh' => sub {return $dbh});
979             $override->inject('DBD::Mock::db::last_insert_rowid', sub {$dbh->{mock_last_insert_id}});
980              
981             my $num_rows_updated = DB::Media::Manager->update_media(
982             set => {
983             location => '/data/music/claire_de_lune.ogg',
984             },
985             where => [
986             id => 2,
987             ],
988             );
989              
990             # support for begin_work, autocommit and rollback
991             my $login_history = DB::UserLoginHistory->new( user_id => 1 );
992             $login_history->db()->dbh()->begin_work();
993             try {
994             $login_history->save();
995             $login_history->db()->dbh()->commit();
996             }
997             catch {
998             $login_history->db()->dbh()->rollback();
999             };
1000              
1001             =head1 DESCRIPTION
1002              
1003             When a real DBI database handle ($dbh) is provided, the module generates C<DBD::Mock::Session> data and stores it in a JSON file.
1004             After the data is generated, remove the 'dbh' argument from the constructor, and it will use the previously generated data to create a 'DBD::Mock::Session' database handle.
1005             Mocked data can also be loaded from a custom file or as a data structure.
1006             This is not a part of the DBD::Mock::Session distribution; it's just a wrapper around it."
1007              
1008             =head1 METHODS
1009              
1010             =head2 new(\%args_for)
1011              
1012             Constructor method to create a new C<DBD::Mock::Session::GenerateFixtures> object.
1013              
1014             Accepts an optional hash reference C<\%args_for> with the following keys:
1015              
1016             =over 4
1017              
1018             =item * C<file>: File path to the fixture file containing mocked data.
1019              
1020             =item * C<data>: Reference to an array containing mock data.
1021              
1022             =item * C<dbh>: Database handle used for reading the data required to genereate a mocked dbh. This should used first time you are runnig the tests.
1023              
1024             =back
1025              
1026             =head2 get_dbh()
1027              
1028             Returns the mocked database handle object.
1029              
1030             =head2 get_override_object()
1031              
1032             Returns the override object used for mocking DBI methods.
1033              
1034             =head2 restore_all()
1035              
1036             Restores all overridden DBI methods to their original implementations.
1037              
1038             This method is used to revert all DBI method overrides set up for mocking database interactions back to their original implementations.
1039              
1040             Returns the current object.
1041              
1042             =head1 PRIVATE METHODS
1043              
1044             These methods are not intended to be called directly from outside the module.
1045              
1046             =head2 _initialize(\%args_for)
1047              
1048             Initializes the C<DBD::Mock::Session::GenerateFixtures> object with the provided arguments.
1049              
1050             =head2 _set_mock_dbh(\@data)
1051              
1052             Sets up the mocked database handle based on the provided data.
1053              
1054             =head2 _override_dbi_methods()
1055              
1056             Overrides various DBI methods for mocking database interactions.
1057              
1058             =head2 _override_dbi_execute($dbi_execute)
1059              
1060             Overrides the C<execute> method of C<DBI::st> in order to capture the sql statement, bound_params and column names.
1061              
1062             =head2 _override_dbi_bind_param($bind_param)
1063              
1064             Overrides the C<bind_param> method of C<DBI::st> in order to capture the bound params.
1065              
1066             =head2 _override_dbi_fetchrow_hashref($fetchrow_hashref)
1067              
1068             Overrides the C<fetchrow_hashref> method of C<DBI::st> in order to capture the rows returned.
1069              
1070             =head2 _override_dbi_fetchrow_arrayref($fetchrow_arrayref)
1071              
1072             Overrides the C<fetchrow_arrayref> method of C<DBI::st> in order to capture the rows returned.
1073              
1074             =head2 _override_dbi_fetchrow_array($fetchrow_array)
1075              
1076             Overrides the C<fetchrow_array> method of C<DBI::st> in order to capture the rows returned.
1077              
1078             =head2 _override_dbi_selectall_arrayref($selectall_arrayref)
1079              
1080             Overrides the C<selectall_arrayref> method of C<DBI::db> in order to capture the rows returned.
1081              
1082             =head2 _override_dbi_selectall_hashref($selectall_hashref)
1083              
1084             Overrides the C<selectall_hashref> method of C<DBI::db> in order to capture the rows returned.
1085              
1086             =head2 _override_dbi_selectcol_arrayref($selectcol_arrayref)
1087              
1088             Overrides the C<selectcol_arrayref> method of C<DBI::db> in order to capture the rows returned.
1089              
1090             =head2 _override_dbi_selectrow_array($selectrow_array)
1091              
1092             Overrides the C<selectrow_array> method of C<DBI::db> in order to capture the rows returned.
1093              
1094             =head2 _override_dbi_selectrow_arrayref($selectrow_arrayref)
1095              
1096             Overrides the C<selectrow_arrayref> method of C<DBI::db> in order to capture the rows returned.
1097              
1098             =head2 _override_dbi_selectrow_hashref($selectrow_hashref)
1099              
1100             Overrides the C<selectrow_hashref> method of C<DBI::db> in order to capture the rows returned.
1101              
1102             =head2 _override_dbi_fetch($sth, @args)
1103              
1104             Overrides the C<fetch> method of C<DBI::st>
1105              
1106             =head2 _override_dbi_prepare
1107              
1108             _override_dbi_prepare($prepare);
1109              
1110             This method overrides the `DBI::db::prepare` method. It customizes how SQL statements are prepared for execution
1111              
1112             =head2 _override_dbi_prepare_cached
1113              
1114             _override_dbi_prepare_cached($prepare_cached);
1115              
1116             This method overrides the `DBI::db::prepare_cached` method. It provides a mechanism for caching prepared statements to optimize repeated queries.
1117              
1118             =head2 _override_dbi_mocked_prepare
1119              
1120             _override_dbi_mocked_prepare($mocked_prepare);
1121              
1122             This method overrides the `DBD::Mock::db::prepare` method. It is used for testing purposes to mock the behavior of statement preparation.
1123              
1124             =head2 _override_dbi_begin_work
1125              
1126             _override_dbi_begin_work($dbi_begin_work);
1127              
1128             This method overrides the `DBD::Mock::db::begin` method. It is used for testing purposes to add in fxtures an 'BEGIN WORK' statement.
1129              
1130             =head2 _override_dbi_commit
1131              
1132             _override_dbi_commit($commit);
1133              
1134             This method overrides the `DBD::Mock::db::commit` method. It is used for testing purposes to add in fxtures an 'COMMIT' statement.
1135              
1136             =head2 _override_dbi_rollback
1137              
1138             _override_dbi_rollback($rollback);
1139              
1140             This method overrides the `DBD::Mock::db::rollback` method. It is used for testing purposes to add in fxtures an 'ROLLBACK' statement.
1141              
1142             =head2 _normalize_sql
1143              
1144             _normalize_sql($sql);
1145              
1146             This method normalizes an SQL query string by removing extra whitespace and trimming leading or trailing spaces.
1147              
1148             =head2 _get_current_record_column_names()
1149              
1150             Returns the column names of the current record being processed.
1151              
1152             =head2 _process_mock_data(\@data)
1153              
1154             Processes the mock data before setting up the mocked database handle.
1155              
1156             =head2 _set_fixtures_file($file)
1157              
1158             Sets the file path for the fixture file containing mocked data.
1159              
1160             =head2 _validate_args(\%args_for)
1161              
1162             Validates the arguments passed to the constructor.
1163              
1164             =head2 _write_to_file()
1165              
1166             Writes the current results to the fixture file if override flag is set.
1167              
1168             =head2 _set_hashref_response($sth, $retval)
1169              
1170             Sets the response for hash references fetched from the database.
1171              
1172             =head1 Support
1173              
1174             Bugs should be reported via the CPAN bug tracker at
1175              
1176             https://rt.cpan.org/Public/Bug/Report.html?Queue=DBD-Mock-Session-GenerateFixtures
1177              
1178             For other issues, contact the author.
1179              
1180             =head1 REPOSITORY
1181              
1182             L<DBD-Fixtures|https://github.com/DragosTrif/DBD-Fixtures>
1183              
1184             =head1 AUTHOR
1185              
1186             Dragos Trif <drd.trif@gmail.com>
1187              
1188             =head1 LICENSE
1189              
1190             This library is free software and may be distributed under the same terms
1191             as perl itself.
1192              
1193             =cut