File Coverage

lib/Test/Neo4j/Types.pm
Criterion Covered Total %
statement 304 304 100.0
branch 9 10 90.0
condition 4 12 33.3
subroutine 50 50 100.0
pod 4 4 100.0
total 371 380 97.6


line stmt bran cond sub pod time code
1 3     3   211930 use v5.10;
  3         28  
2 3     3   18 use strict;
  3         5  
  3         62  
3 3     3   14 use warnings;
  3         3  
  3         203  
4              
5             package Test::Neo4j::Types;
6             # ABSTRACT: Tools for testing Neo4j type modules
7             $Test::Neo4j::Types::VERSION = '0.01';
8              
9 3     3   19 use Test::More 0.94;
  3         39  
  3         15  
10 3     3   2279 use Test::Exception;
  3         10508  
  3         9  
11 3     3   2394 use Test::Warnings qw(warnings :no_end_test);
  3         6112  
  3         15  
12              
13 3     3   371 use Exporter 'import';
  3         5  
  3         150  
14 3     3   166 BEGIN { our @EXPORT = qw(
15             neo4j_node_ok
16             neo4j_relationship_ok
17             neo4j_path_ok
18             neo4j_point_ok
19             )}
20              
21             {
22             # This happens within new versions of Neo4j/Types.pm,
23             # but we can't be sure the version is new enough:
24             package # local
25             Neo4j::Types;
26 3     3   21 use warnings::register;
  3         4  
  3         1022  
27             }
28              
29              
30             sub _element_id_test {
31 12     12   39 my ($BOTH, $ID_ONLY, $new, $class, $prefix) = @_;
32            
33             subtest "${prefix}element_id", sub {
34 12     12   9946 plan tests => 6;
35            
36 12         7623 my $both = $new->($class, {%$BOTH});
37 12         144 my $id_only = $new->($class, {%$ID_ONLY});
38 12 100       163 lives_ok { $both->element_id } 'optional op element_id' if $both->can('element_id');
  4         156  
39 12 100       1583 dies_ok { $both->element_id } 'optional op element_id' if ! $both->can('element_id');
  8         351  
40             SKIP: {
41 12 100       3096 skip 'optional op element_id unimplemented', 2+3 unless $class->can('element_id');
  12         90  
42 3     3   22 no strict 'refs';
  3         5  
  3         640  
43 4         9 my ($element_id, $id) = map { "$prefix$_" } qw( element_id id );
  8         24  
44            
45             # When both IDs are present, id() MAY warn
46 4         16 is $both->$element_id(), $BOTH->{$element_id}, "$element_id";
47 4         1595 warnings { is $both->$id(), $BOTH->{$id}, "legacy $id" };
  4         67  
48            
49             # For a missing element ID, element_id() returns the numeric ID and MUST warn
50 4         1580 my @w_eid = warnings { is $id_only->$element_id(), $ID_ONLY->{$id}, "no $element_id with legacy $id" };
  4         55  
51 4         1589 ok @w_eid, "no $element_id warns";
52 4 50       1475 warn @w_eid if @w_eid > 1;
53 3     3   21 no warnings 'Neo4j::Types';
  3         4  
  3         10412  
54 4         25 is warnings { $id_only->$element_id() }, @w_eid - 1, "no $element_id warn cat is Neo4j::Types";
  4         65  
55             };
56 12         113 };
57             }
58              
59              
60             sub _node_test {
61 3     3   20 my ($node_class, $new) = @_;
62            
63 3         13 plan tests => 12 + 5 + 7 + 1 + 1;
64            
65 3         1871 my ($n, @l, $p);
66            
67 3         47 $n = $new->($node_class, my $id_only = {
68             id => 42,
69             labels => ['Foo', 'Bar'],
70             properties => { foofoo => 11, barbar => 22, '123' => [1, 2, 3] },
71             });
72 3         41 is $n->id(), 42, 'id';
73 3         1312 @l = $n->labels;
74 3         54 is scalar(@l), 2, 'label count';
75 3         1123 is $l[0], 'Foo', 'label Foo';
76 3         1169 is $l[1], 'Bar', 'label Bar';
77 3     3   1086 lives_and { is scalar($n->labels), 2 } 'scalar context';
  3         78  
78 3         1177 is $n->get('foofoo'), 11, 'get foofoo';
79 3         1082 is $n->get('barbar'), 22, 'get barbar';
80 3         1081 is_deeply $n->get('123'), [1, 2, 3], 'get 123';
81 3         2054 $p = $n->properties;
82 3         26 is ref($p), 'HASH', 'props ref';
83 3         1129 is $p->{foofoo}, 11, 'props foofoo';
84 3         1080 is $p->{barbar}, 22, 'props barbar';
85 3         1135 is_deeply $p->{123}, [1, 2, 3], 'props 123';
86            
87 3         1757 $n = $new->($node_class, {
88             id => 0,
89             properties => { '0' => [] },
90             });
91 3         38 is $n->id(), 0, 'id 0';
92 3         1105 is ref($n->get('0')), 'ARRAY', 'get 0 ref';
93 3         1076 is scalar(@{$n->get('0')}), 0, 'get 0 empty';
  3         14  
94 3         1109 $p = $n->properties;
95 3         31 is_deeply $p, {0=>[]}, 'props deeply';
96 3         2213 is_deeply [$n->properties], [{0=>[]}], 'props list context';
97            
98 3         2602 $n = $new->($node_class, { });
99 3         31 ok ! defined($n->id), 'id gigo';
100 3         1079 @l = $n->labels;
101 3         44 is scalar(@l), 0, 'no labels';
102 3     3   1119 lives_and { is scalar($n->labels), 0 } 'scalar context no labels';
  3         50  
103 3         1157 $p = $n->properties;
104 3         28 is ref($p), 'HASH', 'empty props ref';
105 3         1296 is scalar(keys %$p), 0, 'empty props empty';
106 3         1143 is_deeply [$n->get('whatever')], [undef], 'prop undef';
107 3         1768 ok ! exists $n->properties->{whatever}, 'prop remains non-existent';
108            
109             # element ID
110 3         1025 my $both = { element_id => 'e17', id => 17 };
111 3         44 _element_id_test($both, $id_only, $new, $node_class, '');
112            
113 3         8632 ok $n->DOES('Neo4j::Types::Node'), 'does role';
114             }
115              
116              
117             sub neo4j_node_ok {
118 3     3 1 2870 my ($class, $new, $name) = @_;
119 3   33     40 $name //= "neo4j_node_ok '$class'";
120 3     3   19 subtest $name, sub { _node_test($class, $new) };
  3         2629  
121             }
122              
123              
124             sub _relationship_test {
125 3     3   11 my ($rel_class, $new) = @_;
126            
127 3         12 plan tests => 11 + 5 + 8 + 3 + 1;
128            
129 3         1646 my ($r, $p);
130            
131 3         35 $r = $new->($rel_class, my $id_only = {
132             id => 55,
133             type => 'TEST',
134             start_id => 34,
135             end_id => 89,
136             properties => { foo => 144, bar => 233, '358' => [3, 5, 8] },
137             });
138 3         50 is $r->id, 55, 'id';
139 3         1122 is $r->type, 'TEST', 'type';
140 3         1084 is $r->start_id, 34, 'start id';
141 3         1125 is $r->end_id, 89, 'end id';
142 3         1090 is $r->get('foo'), 144, 'get foo';
143 3         1102 is $r->get('bar'), 233, 'get bar';
144 3         1071 is_deeply $r->get('358'), [3, 5, 8], 'get 358';
145 3         1799 $p = $r->properties;
146 3         26 is ref($p), 'HASH', 'props ref';
147 3         1091 is $p->{foo}, 144, 'props foo';
148 3         1120 is $p->{bar}, 233, 'props bar';
149 3         1112 is_deeply $p->{358}, [3, 5, 8], 'props 358';
150            
151 3         1836 $r = $new->($rel_class, {
152             id => 0,
153             properties => { '0' => [] },
154             });
155 3         59 is $r->id(), 0, 'id 0';
156 3         1083 is ref($r->get('0')), 'ARRAY', 'get 0 ref';
157 3         1069 is scalar(@{$r->get('0')}), 0, 'get 0 empty';
  3         12  
158 3         1066 $p = $r->properties;
159 3         30 is_deeply $p, {0=>[]}, 'props deeply';
160 3         2207 is_deeply [$r->properties], [{0=>[]}], 'props list context';
161            
162 3         2625 $r = $new->($rel_class, { });
163 3         34 ok ! defined($r->id), 'id gigo';
164 3         1065 ok ! defined($r->type), 'no type';
165 3         1046 ok ! defined($r->start_id), 'no start id';
166 3         989 ok ! defined($r->end_id), 'no end id';
167 3         1028 $p = $r->properties;
168 3         24 is ref($p), 'HASH', 'empty props ref';
169 3         1113 is scalar(keys %$p), 0, 'empty props empty';
170 3         1099 is_deeply [$r->get('whatever')], [undef], 'prop undef';
171 3         1718 ok ! exists $r->properties->{whatever}, 'prop remains non-existent';
172            
173             # element ID
174 3         1054 my $both = {
175             element_id => 'e60', id => 60,
176             start_element_id => 'e61', start_id => 61,
177             end_element_id => 'e62', end_id => 62,
178             };
179 3         14 _element_id_test($both, $id_only, $new, $rel_class, '');
180 3         8041 _element_id_test($both, $id_only, $new, $rel_class, 'start_');
181 3         7936 _element_id_test($both, $id_only, $new, $rel_class, 'end_');
182            
183 3         7824 ok $r->DOES('Neo4j::Types::Relationship'), 'does role';
184             }
185              
186              
187             sub neo4j_relationship_ok {
188 3     3 1 4082 my ($class, $new, $name) = @_;
189 3   33     30 $name //= "neo4j_relationship_ok '$class'";
190 3     3   15 subtest $name, sub { _relationship_test($class, $new) };
  3         2183  
191             }
192              
193              
194             sub _path_test {
195 2     2   15 my ($path_class, $new) = @_;
196            
197 2         13 plan tests => 3 + 3 + 6 + 6 + 1;
198            
199 2         1101 my (@p, $p, @e);
200            
201             my $new_path = sub {
202 6     6   10 my $i = 0;
203 6 100       11 map { my $o = $_; bless \$o, 'Test::Neo4j::Types::Path' . ($i++ & 1 ? 'Rel' : 'Node') } @_;
  18         22  
  18         78  
204 2         18 };
205            
206 2         8 @p = $new_path->( \6, \7, \8 );
207 2         10 $p = $new->($path_class, \@p);
208 2         24 @e = $p->elements;
209 2         54 is_deeply [@e], [@p], 'deeply elements 3';
210 2         1969 @e = $p->nodes;
211 2         57 is_deeply [@e], [$p[0],$p[2]], 'deeply nodes 2';
212 2         1605 @e = $p->relationships;
213 2         41 is_deeply [@e], [$p[1]], 'deeply rel 1';
214            
215 2         1386 @p = $new_path->( \9 );
216 2         11 $p = $new->($path_class, \@p);
217 2         18 @e = $p->elements;
218 2         35 is_deeply [@e], [@p], 'deeply elements 1';
219 2         1382 @e = $p->nodes;
220 2         25 is_deeply [@e], [$p[0]], 'deeply nodes 1';
221 2         1336 @e = $p->relationships;
222 2         26 is_deeply [@e], [], 'deeply rel 0';
223            
224 2         1074 @p = $new_path->( \1, \2, \3, \4, \5 );
225 2         9 $p = $new->($path_class, \@p);
226 2         16 @e = $p->elements;
227 2         31 is_deeply [@e], [@p], 'deeply elements 5';
228 2     2   2314 lives_and { is scalar($p->elements), 5 } 'scalar context elements';
  2         35  
229 2         888 @e = $p->nodes;
230 2         32 is_deeply [@e], [$p[0],$p[2],$p[4]], 'deeply nodes 3';
231 2     2   1974 lives_and { is scalar($p->nodes), 3 } 'scalar context nodes';
  2         35  
232 2         832 @e = $p->relationships;
233 2         32 is_deeply [@e], [$p[1],$p[3]], 'deeply rel 2';
234 2     2   1621 lives_and { is scalar($p->relationships), 2 } 'scalar context relationships';
  2         31  
235            
236 2         748 $p = $new->($path_class, []);
237 2         19 @e = $p->elements;
238 2         22 is scalar(@e), 0, 'no elements gigo';
239 2     2   726 lives_and { is scalar($p->elements), 0 } 'scalar context no elements';
  2         34  
240 2         738 @e = $p->nodes;
241 2         21 is scalar(@e), 0, 'no nodes 0 gigo';
242 2     2   721 lives_and { is scalar($p->nodes), 0 } 'scalar context no nodes';
  2         32  
243 2         740 @e = $p->relationships;
244 2         23 is scalar(@e), 0, 'no relationships 0 gigo';
245 2     2   720 lives_and { is scalar($p->relationships), 0 } 'scalar context no relationships';
  2         31  
246            
247 2         755 ok $p->DOES('Neo4j::Types::Path'), 'does role';
248             }
249              
250              
251             sub neo4j_path_ok {
252 2     2 1 2708 my ($class, $new, $name) = @_;
253 2   33     18 $name //= "neo4j_path_ok '$class'";
254 2     2   12 subtest $name, sub { _path_test($class, $new) };
  2         1443  
255             }
256              
257              
258             sub _point_test {
259 2     2   7 my ($point_class) = @_;
260            
261 2         8 plan tests => 9+3 + 9+3+3+3+2 + 6+6+6+6 + 1;
262            
263 2         1103 my (@c, $p);
264            
265            
266             # Simple point, location in real world
267 2         16 @c = ( 2.294, 48.858, 396 );
268 2         21 $p = $point_class->new( 4979, @c );
269 2         47 is $p->srid(), 4979, 'eiffel srid';
270 2         734 is $p->X(), 2.294, 'eiffel X';
271 2         724 is $p->Y(), 48.858, 'eiffel Y';
272 2         738 is $p->Z(), 396, 'eiffel Z';
273 2         728 is $p->longitude(), 2.294, 'eiffel lon';
274 2         753 is $p->latitude(), 48.858, 'eiffel lat';
275 2         747 is $p->height(), 396, 'eiffel ellipsoidal height';
276 2         731 is_deeply [$p->coordinates], [@c], 'eiffel coords';
277 2         1226 is scalar ($p->coordinates), 3, 'scalar context eiffel coords';
278            
279 2         754 @c = ( 2.294, 48.858 );
280 2         11 $p = $point_class->new( 4326, @c );
281 2         45 is $p->srid(), 4326, 'eiffel 2d srid';
282 2         747 is_deeply [$p->coordinates], [@c], 'eiffel 2d coords';
283 2         1222 is scalar ($p->coordinates), 2, 'scalar context eiffel 2d coords';
284            
285            
286             # Other SRSs, location not in real world
287 2         738 @c = ( 12, 34 );
288 2         10 $p = $point_class->new( 7203, @c );
289 2         42 is $p->srid(), 7203, 'plane srid';
290 2         716 is $p->X(), 12, 'plane X';
291 2         742 is $p->Y(), 34, 'plane Y';
292 2         722 ok ! defined $p->Z(), 'plane Z';
293 2         718 is $p->longitude(), 12, 'plane lon';
294 2         723 is $p->latitude(), 34, 'plane lat';
295 2         713 ok ! defined $p->height(), 'plane height';
296 2         714 is_deeply [$p->coordinates], [@c], 'plane coords';
297 2         1164 is scalar ($p->coordinates), 2, 'scalar context plane coords';
298            
299 2         746 @c = ( 56, 78, 90 );
300 2         12 $p = $point_class->new( 9157, @c );
301 2         39 is $p->srid(), 9157, 'space srid';
302 2         710 is_deeply [$p->coordinates], [@c], 'space coords';
303 2         1264 is scalar ($p->coordinates), 3, 'scalar context space coords';
304            
305 2         732 @c = ( 361, -91 );
306 2         10 $p = $point_class->new( 4326, @c );
307 2         36 is $p->srid(), 4326, 'ootw srid';
308 2         730 is_deeply [$p->coordinates], [@c], 'ootw coords';
309 2         1192 is scalar ($p->coordinates), 2, 'scalar context ootw coords';
310            
311 2         722 @c = ( 'what', 'ever' );
312 2         12 $p = $point_class->new( '4326', @c );
313 2         45 is $p->srid(), '4326', 'string srid';
314 2         750 is_deeply [$p->coordinates], [@c], 'string coords';
315 2         1205 is scalar ($p->coordinates), 2, 'scalar context string coords';
316            
317 2         717 @c = ( undef, 45 );
318 2         11 $p = $point_class->new( 7203, @c );
319 2         45 is_deeply [$p->coordinates], [@c], 'undef coord';
320 2         1171 is scalar ($p->coordinates), 2, 'scalar context undef coord';
321            
322            
323             # Failure behaviour for incorrect number of coordinates supplied to the constructor
324 2         786 @c = ( 42 );
325 2     2   27 throws_ok { $point_class->new( 4326, @c ) } qr/\bdimensions\b/i, 'new 4326 X fails';
  2         88  
326 2     2   1324 throws_ok { $point_class->new( 4979, @c ) } qr/\bdimensions\b/i, 'new 4979 X fails';
  2         69  
327 2     2   1151 throws_ok { $point_class->new( 7203, @c ) } qr/\bdimensions\b/i, 'new 7203 X fails';
  2         92  
328 2     2   1212 throws_ok { $point_class->new( 9157, @c ) } qr/\bdimensions\b/i, 'new 9157 X fails';
  2         78  
329 2     2   1103 throws_ok { $point_class->new( 12345, @c ) } qr/\bUnsupported\b/i, 'new 12345 X fails';
  2         65  
330 2     2   1132 throws_ok { $point_class->new( undef, @c ) } qr/\bSRID\b/i, 'new undef X fails';
  2         68  
331            
332 2         1072 @c = ( 2.294, 48.858 );
333 2         22 $p = $point_class->new( 4326, @c );
334 2         42 is_deeply [$p->coordinates], [@c[0..1]], 'new 4326';
335 2     2   1268 throws_ok { $point_class->new( 4979, @c ) } qr/\bdimensions\b/i, 'new 4979 XY fails';
  2         75  
336 2         1200 $p = $point_class->new( 7203, @c );
337 2         40 is_deeply [$p->coordinates], [@c[0..1]], 'new 7203';
338 2     2   1291 throws_ok { $point_class->new( 9157, @c ) } qr/\bdimensions\b/i, 'new 9157 XY fails';
  2         69  
339 2     2   1102 throws_ok { $point_class->new( 12345, @c ) } qr/\bUnsupported\b/i, 'new 12345 XY fails';
  2         80  
340 2     2   1100 throws_ok { $point_class->new( undef, @c ) } qr/\bSRID\b/i, 'new undef XY fails';
  2         65  
341            
342 2         1123 @c = ( 2.294, 48.858, 396 );
343 2         9 $p = $point_class->new( 4326, @c );
344 2         43 is_deeply [$p->coordinates], [@c[0..1]], 'new 4326 Z ignored';
345 2         1233 $p = $point_class->new( 4979, @c );
346 2         38 is_deeply [$p->coordinates], [@c[0..2]], 'new 4979';
347 2         1207 $p = $point_class->new( 7203, @c );
348 2         43 is_deeply [$p->coordinates], [@c[0..1]], 'new 7203 Z ignored';
349 2         1147 $p = $point_class->new( 9157, @c );
350 2         36 is_deeply [$p->coordinates], [@c[0..2]], 'new 9157';
351 2     2   1217 throws_ok { $point_class->new( 12345, @c ) } qr/\bUnsupported\b/i, 'new 12345 XYZ fails';
  2         68  
352 2     2   1198 throws_ok { $point_class->new( undef, @c ) } qr/\bSRID\b/i, 'new undef XYZ fails';
  2         88  
353            
354 2         1229 @c = ( 2.294, 48.858, 396, 13 );
355 2         12 $p = $point_class->new( 4326, @c );
356 2         39 is_deeply [$p->coordinates], [@c[0..1]], 'new 4326 ZM ignored';
357 2         1245 $p = $point_class->new( 4979, @c );
358 2         36 is_deeply [$p->coordinates], [@c[0..2]], 'new 4979 M ignored';
359 2         1173 $p = $point_class->new( 7203, @c );
360 2         81 is_deeply [$p->coordinates], [@c[0..1]], 'new 7203 ZM ignored';
361 2         1173 $p = $point_class->new( 9157, @c );
362 2         38 is_deeply [$p->coordinates], [@c[0..2]], 'new 9157 M ignored';
363 2     2   1298 throws_ok { $point_class->new( 12345, @c ) } qr/\bUnsupported\b/i, 'new 12345 XYZM fails';
  2         94  
364 2     2   1123 throws_ok { $point_class->new( undef, @c ) } qr/\bSRID\b/i, 'new undef XYZM fails';
  2         132  
365            
366            
367 2         1226 ok $p->DOES('Neo4j::Types::Point'), 'does role';
368             }
369              
370              
371             sub neo4j_point_ok {
372 2     2 1 2662 my ($class, $name) = @_;
373 2   33     20 $name //= "neo4j_point_ok '$class'";
374 2     2   14 subtest $name, sub { _point_test($class) };
  2         1491  
375             }
376              
377              
378             package # private
379             Test::Neo4j::Types::PathNode;
380 12     12   56 sub DOES { $_[1] eq 'Neo4j::Types::Node' }
381              
382              
383             package # private
384             Test::Neo4j::Types::PathRel;
385 6     6   18 sub DOES { $_[1] eq 'Neo4j::Types::Relationship' }
386              
387              
388             1;