File Coverage

blib/lib/EntityModel/Test/Storage.pm
Criterion Covered Total %
statement 46 97 47.4
branch 1 46 2.1
condition 4 15 26.6
subroutine 12 18 66.6
pod 2 2 100.0
total 65 178 36.5


line stmt bran cond sub pod time code
1             package EntityModel::Test::Storage;
2             {
3             $EntityModel::Test::Storage::VERSION = '0.102';
4             }
5             use EntityModel::Class {
6 1         9 _isa => [qw(Exporter)],
7 1     1   24508 };
  1         75556  
8 1     1   1958 no if $] >= 5.017011, warnings => "experimental::smartmatch";
  1         1  
  1         6  
9              
10             =head1 NAME
11              
12             EntityModel::Test::Storage - tests for L and subclasses
13              
14             =head1 VERSION
15              
16             version 0.102
17              
18             =head1 SYNOPSIS
19              
20             use EntityModel::Test::Storage;
21             storage_ok('EntityModel::Storage::Perl', '::Perl subclass works');
22              
23             =head1 DESCRIPTION
24              
25             Provides functions for testing L subclasses.
26              
27             =cut
28              
29 1     1   61 use Test::Builder;
  1         1  
  1         17  
30 1     1   5 use Module::Load;
  1         2  
  1         6  
31 1     1   782 use CPS qw(kseq);
  1         5459  
  1         56  
32 1     1   720 use EntityModel;
  1         3  
  1         5  
33              
34 1         1738 use constant STORAGE_METHODS => qw(
35             new
36             register
37             apply_model
38             apply_model_and_schema
39             read
40             create
41             store
42             remove
43             find
44             adjacent
45             prev
46             next
47             outer
48             first
49             last
50 1     1   48 );
  1         3  
51              
52             =head1 EXPORTS
53              
54             Since this is a test class, functions are exported automatically
55             to match behaviour of other test modules such as L.
56             To disable this, pass an empty list on the C line or
57             use C instead:
58              
59             use EntityModel::Test::Storage ();
60             EntityModel::Test::Storage::storage_ok(...);
61              
62             =cut
63              
64             our @EXPORT = qw(
65             storage_ok
66             storage_methods_ok
67             );
68              
69             =head1 FUNCTIONS
70              
71             =cut
72              
73             =head2 storage_ok
74              
75             Runs all available tests (including attempting to load the module) and returns the usual
76             L ok/fail response.
77              
78             =cut
79              
80             sub storage_ok {
81 1     1 1 12 my $class = shift;
82 1   50     5 my $opt = shift || [];
83 1   33     12 my $msg = shift || "$class is a valid, working EntityModel::Storage (sub)class";
84              
85             # First we need to be able to load our module
86             try {
87 1         9 Module::Load::load($class);
88 1         2 } catch {
89 1         118 return _report_fail($msg, $_);
90             };
91              
92 0 0       0 $class->isa('EntityModel::Storage') or return _report_fail($msg, 'is not an EntityModel::Storage (sub)class');
93 0 0       0 _methods_ok($class, $msg) or return;
94              
95             # Abstract base class won't work very well for 'real' model handling, so skip that
96              
97 0 0       0 unless($class eq 'EntityModel::Storage') {
98 0 0       0 _simple_model($class, $opt, $msg) or return;
99             }
100              
101 0         0 return _report_pass($msg);
102             }
103              
104             =head2 storage_methods_ok
105              
106             Check whether the expected methods are present. Requires the class to be loaded first.
107              
108             =cut
109              
110             sub storage_methods_ok {
111 1     1 1 8 my $class = shift;
112 1   50     3 my $opt = shift || [];
113 1   33     7 my $msg = shift || "$class has all the required methods";
114 1 0       4 return 0 unless _methods_ok($class, $msg);
115 0         0 return _report_pass($msg);
116             }
117              
118             =head2 _methods_ok
119              
120             Internal helper function to report whether the expected methods are present for the subclass.
121              
122             =cut
123              
124             sub _methods_ok {
125 1     1   2 my $class = shift;
126 1         2 my $msg = shift;
127              
128 1         1 my %failed;
129 1         5 foreach my $method (STORAGE_METHODS) {
130             try {
131 1         24 $class->can($method);
132 1 0 0     2 } catch {
133 1         182 $failed{$method} = $_;
134             } or $failed{$method} ||= 'not available';
135             }
136 0 0       0 if(keys %failed) {
137 0         0 return _report_fail($msg, join "\n", map { "Could not $class->$_ because: " . $failed{$_} } sort keys %failed);
  0         0  
138             }
139 0         0 return 1;
140             }
141              
142             =head2 _simple_model
143              
144             =cut
145              
146             sub _simple_model {
147 0     0   0 my $class = shift;
148 0         0 my $opt = shift;
149 0         0 my $msg = shift;
150              
151             # Bring in a simple model - if this fails then it's not the storage class' fault so die() rather than marking as failure
152 0 0       0 my $model = EntityModel->new->load_from(
153             Perl => {
154             "name" => "mymodel",
155             "entity" => [ {
156             "name" => "thing",
157             "primary" => "id",
158             "field" => [
159             { "name" => "id", "type" => "int" },
160             { "name" => "name", "type" => "varchar" }
161             ]
162             }, {
163             "name" => "other",
164             "primary" => "id",
165             "field" => [
166             { "name" => "id", "type" => "int" },
167             { "name" => "extra", "type" => "varchar" }
168             ]
169             } ]
170             }
171             ) or die "Model creation failed";
172              
173             # Now we try to apply the storage model
174 0 0       0 $model->add_storage($class => $opt) or die "Failed to add storage";
175              
176             # Sanity check that we end up with single storage item of the expected class
177 0         0 my @storage = $model->storage->list;
178 0 0       0 return _report_fail($msg, "expected 1 storage item, found " . scalar(@storage)) unless @storage == 1;
179 0 0       0 return _report_fail($msg, "unexpected class found, wanted $class but had " . join(',', map ref, @storage)) if grep { ref($_) ne $class } @storage;
  0         0  
180              
181             # Now the model has been applied, we'll try to do some simple tests directly against storage:
182             # first we'll create an entry
183 0         0 my ($storage) = @storage;
184 0         0 my ($thing) = grep { $_->name eq 'thing' } $model->entity->list;
  0         0  
185 0 0       0 die "no thing" unless $thing;
186              
187             # Support failure passthrough in our continuations
188 0         0 my $failed = 0;
189             my $fail = sub {
190 0     0   0 my ($err, $next) = @_;
191             # Bail out immediately if we're already in failure state.
192 0 0 0     0 $next->() if $failed && $next;
193              
194             # This would be a most lamentable state of affairs
195 0 0       0 die "Failed already and no continuation, help!" if $failed;
196              
197 0         0 $failed = 1;
198 0         0 _report_fail($msg, $err);
199 0 0       0 $next->() if $next;
200 0         0 };
201              
202             # Take advantage of CPS to avoid excessive indentation
203 0         0 my ($id, $data);
204             kseq(sub {
205             # First we create a simple entity instance
206 0     0   0 my $next = pop;
207             $storage->create(
208             entity => $thing,
209             data => {
210             name => 'Test name',
211             },
212             on_complete => sub {
213 0         0 $id = shift;
214 0         0 $next->();
215             },
216             on_fail => sub {
217 0         0 $fail->("Something failed", $next);
218             }
219 0 0       0 ) or $fail->("->create returned false", $next);
220             }, sub {
221             # Next we check, then read it back
222 0     0   0 my $next = pop;
223 0 0       0 $next->() if $failed;
224 0 0       0 $fail->("no ID assigned", $next) unless defined $id;
225              
226             $storage->read(
227             entity => $thing,
228             id => $id,
229             on_complete => sub {
230 0         0 $data = shift;
231 0         0 $next->();
232             },
233             on_fail => sub {
234 0         0 $fail->("Something failed", $next);
235             }
236 0 0       0 ) or $fail->("->read returned false", $next);
237             }, sub {
238             # Verify that we read back what we wrote originally
239 0     0   0 my %read = %$data;
240 0 0       0 $fail->("wrong keys returned: " . join ',', keys %read) unless [sort keys %read] ~~ [qw(id name)];
241 0 0       0 $fail->("wrong data for name - returned: " . $read{name}) unless $read{name} eq 'Test name';
242 0         0 });
243 0 0       0 return 0 if $failed;
244 0         0 return 1;
245             }
246              
247             =head2 _report_status
248              
249             Internal helper function to report pass/fail via L.
250              
251             =cut
252              
253             sub _report_status {
254 1     1   2 my $ok = shift;
255 1         3 my $msg = shift;
256 1         2 my $diag = shift;
257              
258 1         12 my $test = Test::Builder->new;
259 1         19 $test->ok($ok, $msg);
260 1 50       1206 $test->diag($diag) if defined $diag;
261 1         5 return $ok;
262             }
263              
264 0     0   0 sub _report_pass { _report_status(1, @_) }
265              
266 1     1   6 sub _report_fail { _report_status(0, @_) }
267              
268             1;
269              
270             __END__