r1042@llin: dpavlin | 2006-09-29 20:53:01 +0200
[webpac2] / t / 3-normalize.t
1 #!/usr/bin/perl -w
2
3 use strict;
4
5 use Test::More tests => 309;
6 use Test::Exception;
7 use Cwd qw/abs_path/;
8 use blib;
9 use File::Slurp;
10 use Getopt::Long;
11
12 BEGIN {
13         use_ok( 'WebPAC::Normalize' );
14 }
15
16 use Data::Dump qw/dump/;
17
18 my $debug = 0;
19 GetOptions(
20         "debug+", \$debug
21 );
22
23 cmp_ok(_debug(1), '==', 1, '_debug level');
24 cmp_ok(_debug(0), '==', 0, '_debug level');
25
26 diag "debug level for $0 is $debug" if ($debug);
27 if ($debug > 2) {
28         diag "debug level for WebPAC::Normalize is ", _debug( $debug - 2 );
29 }
30
31 ok(my $abs_path = abs_path($0), "abs_path");
32 $abs_path =~ s#/[^/]*$#/#;
33 diag "abs_path: $abs_path" if ($debug);
34
35 my $rec1 = {
36         '200' => [{
37                 'a' => '200a',
38                 'b' => '200b',
39                 },{
40                 'c' => '200c',
41                 'd' => '200d',
42                 },{
43                 'a' => '200a*2',
44                 'd' => '200d*2',
45                 }],
46         '201' => [{
47                 'x' => '201x',
48                 'y' => '201y',
49                 }],
50         '900' => [
51                 '900-no_subfield'
52                 ],
53         '901' => [{
54                 'a' => '900a',
55                 }],
56         '902' => [{
57                 'z' => '900',
58                 }],
59 };
60
61 my $rec2 = {
62  '675' => [ {
63               'a' => '159.9'
64             } ],
65  '210' => [ {
66               'c' => 'New York University press',
67               'a' => 'New York',
68               'd' => 'cop. 1988'
69             } ],
70  '700' => [ {
71               'a' => 'Haynal',
72               'b' => 'AndrĂ©'
73             } ],
74  '801' => [ 'FFZG' ],
75  '991' => [ '8302' ],
76  '000' => [ 1 ],
77  '702' => [ {
78               'a' => 'Holder',
79               'b' => 'Elizabeth'
80             } ],
81  '215' => [ {
82               'c' => 'ilustr',
83               'a' => 'xix, 202 str',
84               'd' => '23cm'
85             } ],
86  '990' => [
87             '2140',
88             '88',
89             'HAY'
90           ],
91  '200' => [ {
92               'e' => 'from Freud and Ferenczi to Michael balint',
93               'a' => 'Controversies in psychoanalytic method',
94               'g' => 'translated by Elizabeth Holder on the basisi of a first draft by Archie Hooton ; with a preface by Daniel N. Stern',
95               'f' => 'by AndrĂ© E. Haynal'
96             } ],
97  '610' => [ 'povijest psihoanalize' ],
98  '994' => [ {
99               'c' => '',
100               'a' => 'PS',
101               'b' => 'MG'
102             } ],
103  '320' => [ 'Kazalo' ],
104  '101' => [ 'ENG' ],
105  '686' => [ '2140' ],
106  '300' => [ 'Prijevod djela: ' ],
107 };
108
109
110 my $lookup_hash1 = {
111         'db1' => {
112                 'input1' => {
113                         'key1' => { 1 => 1 },
114                         'key2' => { 2 => 1 },
115                 },
116                 'input2' => {
117                         'key3' => { 3 => 1 },
118                         'key4' => { 4 => 1 },
119                 },
120         },
121         'db2' => {
122                 'input3' => {
123                         'key5' => { 5 => 1 },
124                         'key6' => { 6 => 1 },
125                 },
126         }
127 };
128
129 my $lookup_hash2 = {
130         'db3' => {
131                 'input4' => {
132                         'key7' => { 7 => 1 },
133                         'key8' => { 8 => 1 },
134                 },
135         }
136 };
137
138 sub test {
139         print dump( @_ ), ("-" x 78), "\n";
140         ok( defined(@_) );
141 }
142
143 # how much of string evaled to display?
144 my $max_eval_output = 170;
145
146 sub dump_error {
147         my ($msg,$code) = @_;
148
149         my @l = split(/[\n\r]/, $code);
150         my $out = "$msg\n";
151
152         foreach my $i ( 0 .. $#l ) {
153                 $out .= sprintf("%2d: %s\n", $i, $l[$i]);
154         }
155
156         return $out;
157 }
158
159 sub test_s {
160         my $t = shift || die;
161
162         my $eval_t = $t;
163         $eval_t =~ s/[\n\r\s]+/ /gs;
164         $eval_t = substr($eval_t,0,$max_eval_output) . '...' if (length($eval_t) > $max_eval_output);
165         $eval_t =~ s/\\/\\\\/gs;
166
167         my @__ret;
168         eval "\@__ret = $t";
169         ok(! $@, $@ ? dump_error($@, $t) : "eval: $eval_t = " . dump(@__ret));
170         return \@__ret;
171 }
172
173 {
174         no strict 'subs';
175         use WebPAC::Normalize;
176
177         ok(! _set_lookup( undef ), "set_lookup(undef)");
178
179         _set_rec( $rec1 );
180
181         cmp_ok( join(",", rec2('200','a') ), 'eq', '200a,200a*2', 'join rec2' );
182         cmp_ok( join(",", rec2('200','a'), rec2('200','b') ), 'eq', '200a,200a*2,200b', 'join rec2 rec2' );
183         cmp_ok( join(" * ", sort(rec1('200'), rec1('201') )), 'eq', '200a * 200a*2 * 200b * 200c * 200d * 200d*2 * 201x * 201y', 'join sort rec1 rec1' );
184         diag "is_deeply checks\n";
185         is_deeply( \[ rec1('200') ], \[ qw/200a 200b 200c 200d 200a*2 200d*2/ ] );
186         is_deeply( \[ regex( 's/0/o/g', rec1('200') ) ],  \[ qw/2ooa 2oob 2ooc 2ood 2ooa*2 2ood*2/ ]);
187         is_deeply( \[ grep { /\*/ } regex( 's/0/o/g', rec1('200') ) ], \[ qw/2ooa*2 2ood*2/ ]);
188         is_deeply( \[ rec('902') ], \[ '900' ] );
189
190         cmp_ok( rec('902'), 'eq', rec('902','z'), 'rec sf' );
191
192         # simple list manipulatons
193         cmp_ok( join('', prefix('ab', 'cd') ), 'eq', 'abcd', 'prefix');
194         cmp_ok( join('', suffix('xy', 'cd') ), 'eq', 'cdxy', 'suffix');
195         cmp_ok( join('', surround('->', '<-', 'a','b','c') ), 'eq', '->a<-->b<-->c<-', 'surround');
196
197         # lookups
198
199         throws_ok { _set_load_ds() } qr/CODE/, 'empty _set_load_ds()';
200
201         ok(_set_load_ds(sub {
202                 my ($database,$input,$mfn) = @_;
203                 diag "load_ds( $database, $input, $mfn )";
204                 cmp_ok( $#_, '==', 2, 'have 3 arguments');
205                 ok($database, '_load_ds database');
206                 ok($input, '_load_ds input');
207                 ok($mfn, '_load_ds mfn');
208                 return {
209                         '900' => [{ x => '900x-' . $mfn , y => '900y-' . $mfn }],
210                 }
211
212         }), '_set_load_ds');
213
214         my @v = qw/foo bar baz aaa bbb ccc ddd/;
215
216         my @accumulated;
217
218         for my $i ( 0 .. $#v ) {
219
220                 my $mfn = 1000 + $i;
221
222                 ok(WebPAC::Normalize::_set_config({ '_mfn' => $mfn }), "_set_config _mfn=$mfn");
223
224                 my $size = $#v + 1;
225
226                 cmp_ok(
227                         save_into_lookup('db','input','key', sub { @v }),
228                         '==', $size, "save_into_lookup $size values"
229                 );
230
231                 ok(my $l = WebPAC::Normalize::_get_lookup(), '_get_lookup');
232                 diag "_get_lookup = ", dump($l);
233
234                 my @lookup;
235
236                 ok(my @lookup = lookup(
237                                 sub {
238                                         diag "in show";
239                                         rec('900','x');
240                                 },
241                                 'db','input','key',
242                                 sub {
243                                         return @v;
244                                 }
245                         ),
246                 "lookup db/input/key");
247
248                 push @accumulated, '900x-' . $mfn;
249
250                 is_deeply(\@lookup, \@accumulated, "lookup db/input/key");
251
252                 shift @v;
253
254         }
255
256         ok(my $l = WebPAC::Normalize::_get_lookup(), '_get_lookup');
257         diag "_get_lookup = ", dump($l);
258
259         is_deeply( $l, {
260                 db => {
261                         input => {
262                                 key => {
263                                         foo => { 1000 => 1 },
264                                         bar => { 1000 => 1, 1001 => 1 },
265                                         baz => { 1000 => 1, 1001 => 1, 1002 => 1 },
266                                         aaa => { 1000 => 1, 1001 => 1, 1002 => 1, 1003 => 1 },
267                                         bbb => { 1000 => 1, 1001 => 1, 1002 => 1, 1003 => 1, 1004 => 1 },
268                                         ccc => { 1000 => 1, 1001 => 1, 1002 => 1, 1003 => 1, 1004 => 1, 1005 => 1 },
269                                         ddd => { 1000 => 1, 1001 => 1, 1002 => 1, 1003 => 1, 1004 => 1, 1005 => 1, 1006 => 1 },
270                                 },
271                         },
272                 },
273         }, 'lookup data');
274
275 #######
276
277         diag "lookup_hash1 = ", dump($lookup_hash1);
278         ok(_set_lookup( $lookup_hash1 ), '_set_lookup $lookup_hash1');
279
280         throws_ok { _set_load_ds() } qr/CODE/, 'empty _set_load_ds()';
281
282         ok(_set_load_ds(sub {
283                 my ($database,$input,$mfn) = @_;
284                 diag "load_ds( $database, $input, $mfn )";
285                 cmp_ok( $#_, '==', 2, 'have 3 arguments');
286                 ok($database, 'database');
287                 ok($input, 'input');
288                 ok($mfn, 'mfn');
289
290         }), '_set_load_ds');
291
292
293 #       cmp_ok(lookup(
294 #               sub {
295 #                       'found'
296 #               },
297 #               'db1','input1','key1',
298 #               sub {
299 #                       rec('200','a')
300 #               }
301 #       ), 'eq', 'found', 'lookup db1/input1/key1');
302
303
304         
305 #       cmp_ok(
306 #               lookup( 
307 #               ),
308 #       'eq', 'lookup 1 i lookup 2', 'join lookup regex rec2');
309
310         # check join_with operations
311
312         sub test_join_with_2 {
313                 my ($a,$b,$e) = @_;
314
315                 cmp_ok(
316                         join_with(" <1> ",
317                                 rec('201',$a),
318                                 rec('201',$b),
319                         ), 
320                 'eq', $e, "join_with $a <1> $b = $e");
321         }
322
323         test_join_with_2('_','_','');
324         test_join_with_2('x','_','201x');
325         test_join_with_2('_','x','201x');
326         test_join_with_2('x','y','201x <1> 201y');
327
328         sub test_join_with_3 {
329                 my ($a,$b,$c,$e) = @_;
330
331                 cmp_ok(
332                         join_with(" <1> ", rec('201',$a),
333                                 join_with(" <2> ", rec('201',$b),
334                                         rec('201',$c),
335                                 )
336                         ), 
337                 'eq', $e, "join_with $a <1> $b <2> $c = $e");
338         };
339
340         test_join_with_3('_','_','_','');
341         test_join_with_3('x','_','_','201x');
342         test_join_with_3('_','x','_','201x');
343         test_join_with_3('_','_','x','201x');
344         test_join_with_3('x','y','_','201x <1> 201y');
345         test_join_with_3('x','_','y','201x <1> 201y');
346         test_join_with_3('_','x','y','201x <2> 201y');
347         test_join_with_3('x','_','y','201x <1> 201y');
348         test_join_with_3('x','y','x','201x <1> 201y <2> 201x');
349
350         # test lookups
351
352         _set_lookup( $lookup_hash2 );
353
354         throws_ok { lookup() } qr/need/, 'empty lookup';
355
356         #is_deeply( \[ lookup( prefix( '00', rec('902') ) ) ], \[ 'lookup' ], 'lookup prefix' );
357
358         #ok(! lookup('non-existent'), 'lookup non-existant' );
359
360         _set_rec( $rec2 );
361
362         test_s(qq{
363                 tag('Title',
364                         rec('200','a')
365                 );
366         });
367         test_s(qq{
368                 tag('Who',
369                         join_with(" ",
370                                 rec('702','a'),
371                                 rec('702','b')
372                         )
373                 );
374         });
375
376         test_s(qq{
377                 display('Publisher',
378                         rec('210','c')
379                 )
380         });
381         
382         test_s(qq{
383                 search('Year',
384                         regex( 's/[^\\d]+//',
385                                 rec('210','d')
386                         )
387                 )
388         });
389
390         ok(my $ds = _get_ds(), "get_ds");
391         diag "ds = ", dump($ds) if ($debug);
392
393
394         sub test_check_ds {
395
396                 my $t = shift;
397
398                 ok($ds = _get_ds(), 'get_ds');
399                 diag dump( $ds ) if ($debug);
400
401                 ok( $ds && $ds->{something}, 'get_ds->something exists' );
402                 ok( $ds && $ds->{something}->{$t}, 'get_ds->something->'.$t.' exists') if ($t);
403                 ok( $ds && !$ds->{empty}, 'get_ds->empty doesn\'t' );
404
405                 return $ds;
406         }
407
408         _clean_ds();
409         test_s(qq{ search('something', '42'); });
410         test_s(qq{ search('empty', ''); });
411         test_check_ds('search');
412
413         _clean_ds();
414         test_s(qq{ display('something', '42'); });
415         test_s(qq{ display('empty', ''); });
416         test_check_ds('display');
417
418         _clean_ds();
419         test_s(qq{ tag('something', '42'); });
420         test_s(qq{ tag('empty', ''); });
421         test_check_ds('search');
422         test_check_ds('display');
423
424         _clean_ds();
425         my $n = read_file( "$abs_path/data/normalize.pl" );
426         $n .= "\n1;\n";
427         #diag "normalize code:\n$n\n";
428         test_s( $n );
429
430         ok($ds = _get_ds(), "get_ds");
431         diag "ds = ", dump($ds) if ($debug);
432
433         my $rec = {
434                 '200' => [{
435                         'a' => '200a',
436                         'b' => '200b',
437                 }],
438         };
439         my $rules = qq{ search('mixed', rec('200') ) };
440         
441         _clean_ds();
442         _set_rec( $rec );
443         test_s( $rules );
444         ok($ds = _get_ds(), "get_ds");
445         is_deeply( $ds, {
446                 'mixed' => {
447                         'search' => [ '200a', '200b' ],
448                         'tag' => 'mixed'
449                 }
450         }, 'correct get_ds');
451
452         ok(my $ds2 = WebPAC::Normalize::data_structure(
453                 row => $rec,
454                 rules => $rules,
455         ), 'data_structure');
456         is_deeply( $ds, $ds2, 'data_structure(s) same');
457
458         # wird and non-valid structure which is supported anyway
459         _clean_ds();
460         _set_rec({
461                 '200' => [{
462                         'a' => '200a',
463                 },
464                         '200-solo'
465                 ]
466         });
467         test_s(qq{ search('mixed', rec('200') ) });
468         ok($ds = _get_ds(), "get_ds");
469         is_deeply( $ds, {
470                 'mixed' => {
471                         'search' => [ '200a', '200-solo' ],
472                         'tag' => 'mixed'
473                 }
474         }, 'correct get_ds');
475
476         #
477         # MARC
478         #
479         #_debug( 4 );
480
481         test_s(qq{ marc_indicators('900',1,2) });
482         test_s(qq{ marc('900','a', rec('200') ) });
483         my $marc;
484         ok($marc = WebPAC::Normalize::_get_marc_fields(), "_get_marc_fields");
485         diag dump( $marc ) if ($debug);
486
487         is_deeply( $marc, [
488                 [ '900', 1, 2, 'a', '200a' ],
489                 [ '900', 1, 2, 'a', '200-solo' ]
490         ], 'correct marc with indicators');
491
492         test_s(qq{ marc_indicators('900',' ',9) });
493         test_s(qq{ marc_repeatable_subfield('900','a', rec('200') ) });
494
495         ok($marc = WebPAC::Normalize::_get_marc_fields(), "_get_marc_fields");
496         diag dump( $marc ) if ($debug);
497
498         is_deeply( $marc, [
499                 [ '900', 1, 2, 'a', '200a', 'a', '200-solo' ],
500                 [ '900', ' ', 9, 'a', '200a', 'a', '200-solo' ]
501         ], 'correct marc with repetable subfield');
502
503         #
504         # test magic re-ordering of input data
505         #
506
507         sub test_rec_rules {
508                 my ($msg, $rec, $rules, $struct) = @_;
509
510                 _clean_ds();
511                 _set_rec($rec);
512
513                 foreach my $r (split(/;/, $rules)) {
514                         $r =~ s/[\s\n\r]+/ /gs;
515                         $r =~ s/^\s+//gs;
516                         $r =~ s/\s+$//gs;
517                         test_s($r) if ($r);
518                 }
519
520                 ok(my $marc = WebPAC::Normalize::_get_marc_fields(), "_get_marc_fields");
521                 diag dump( $marc ) if ($debug);
522                 diag "expects:\n", dump($struct) if ($debug > 1);
523                 is_deeply( $marc, $struct, $msg );
524         }
525
526         test_rec_rules(
527                 'correct marc with repetable subfield',
528                 {
529                         '200' => [{
530                                 'a' => '200a-1',
531                                 'b' => '200b-1',
532                                 'c' => '200c-1',
533                         }, {
534                                 'a' => '200a-2',
535                                 'b' => '200b-2',
536                         }, {
537                                 'a' => '200a-3',
538                         }],
539                 },
540                 qq{
541                         marc_indicators('900',1 ,0);
542                         marc('900','a', rec('200','a') );
543                         marc('900','b', rec('200','b') );
544                         marc('900','c', rec('200','c') );
545                 },
546                 [
547                         [ '900', 1, 0, 'a', '200a-1', 'b', '200b-1', 'c', '200c-1' ],
548                         [ '900', 1, 0, 'a', '200a-2', 'b', '200b-2' ],
549                         [ '900', 1, 0, 'a', '200a-3' ],
550                 ],
551         );
552
553
554         test_rec_rules(
555                 'marc_repeatable_subfield',
556                 {
557                         '200' => [{
558                                 'a' => '200a-1',
559                                 'b' => '200b-1',
560                                 'c' => '200c-1',
561                         }, {
562                                 'a' => '200a-2',
563                                 'b' => '200b-2',
564                                 'c' => '200c-2',
565                         }, {
566                                 'a' => '200a-3',
567                                 'c' => '200c-3',
568                         }],
569                 },
570                 qq{
571                         marc_indicators('900',1 ,0);
572                         marc_repeatable_subfield('900','a', rec('200','a') );
573                         marc('900','b', rec('200','b') );
574                         marc('900','c', rec('200','c') );
575                 },
576                 [
577                         [ '900', 1, 0, 'a', '200a-1', 'a', '200a-2', 'a', '200a-3', 'b', '200b-1', 'c', '200c-1' ],
578                         [ '900', 1, 0, 'b', '200b-2', 'c', '200c-2' ],
579                         [ '900', 1, 0, 'c', '200c-3' ],
580                 ],
581         );
582
583         test_rec_rules(
584                 'marc_compose',
585                 { '200' => [{ a => 'foo ; bar', b => 42, c => 'baz' }] },
586                 qq{
587                         marc_compose('900',
588                                 'c', rec(200,'b'),
589                                 'b', rec(200,'a'),
590                                 'a', rec(200,'c'),
591                         );
592                 },
593                 [
594                         [ '900', ' ', ' ', 'c', 42, 'b', 'foo ; bar', 'a', 'baz' ]
595                 ],
596         );
597
598         test_rec_rules(
599                 'marc_compose with + subfields',
600                 { '200' => [{ a => 'foo ; bar', b => 42, c => 'baz' }] },
601                 qq{
602                         marc_compose('900',
603                                 'a', rec(200,'a'),
604                                 '+', prefix(" * ", rec(200,'c')),
605                                 'b', rec(200,'b'),
606                                 '+', prefix(" : ", rec(200,'c')),
607                         );
608                 },
609                 [
610                         [ '900', ' ', ' ', 'a', 'foo ; bar * baz', 'b', '42 : baz' ]
611                 ],
612         );
613
614         #
615         # test rules
616         #
617         sub test_rule {
618                 my ($msg, $rec, $rule, $struct) = @_;
619                 _clean_ds();
620                 _set_rec( $rec );
621                 $rule =~ s/\\/\\/gs;
622                 my $r = test_s( $rule );
623                 diag "for ", dump($rec), " got:\n", dump($r), "\nexpect:\n" if ($debug > 1);
624                 diag dump($struct) if ($debug);
625                 is_deeply( $r, $struct, $msg );
626         }
627
628         # test split_rec_on
629         test_rule(
630                 'split_rec_on',
631                 { '200' => [{ a => 'foo ; bar', b => 42, c => 'baz' }] },
632                 qq{ split_rec_on('200','a', qr/\\s*;\\s*/, 1) },
633                 [ 'foo' ],
634         );
635         test_rule(
636                 'split_rec_on',
637                 { '200' => [{ a => 'foo ; bar', b => 42, c => 'baz' }] },
638                 qq{ split_rec_on('200','a', qr/\\s*;\\s*/, 2) },
639                 [ 'bar' ],
640         );
641         test_rule(
642                 'split_rec_on no part',
643                 { '200' => [{ a => 'foo ; bar', b => 42, c => 'baz' }] },
644                 qq{ split_rec_on('200','a', qr/\\s*;\\s*/) },
645                 [ 'foo', 'bar' ],
646         );
647         test_rule(
648                 'split_rec_on no record',
649                 {},
650                 qq{ split_rec_on('200','a', qr/\\s*;\\s*/) },
651                 [ '' ],
652         );
653
654         test_rec_rules(
655                 'marc_compose+split_rec_on',
656                 { '200' => [{ a => 'foo ! bar', b => 42, c => 'baz' }] },
657                 qq{
658                         marc_compose('900',
659                                 'a', split_rec_on(200,'a', qr/\\s*!\\s*/, 1),
660                                 'c', rec(200,'c'),
661                                 'a', split_rec_on(200,'a', qr/\\s*!\\s*/, 2),
662                                 'b', rec(200,'b'),
663                         );
664                 },
665                 [
666                         [ '900', ' ', ' ',
667                                 'a', 'foo',
668                                 'c', 'baz',
669                                 'a', 'bar',
670                                 'b', 42,
671                         ]
672                 ],
673         );
674
675         cmp_ok(marc_leader('06',42), '==', 42, 'marc_leader');
676         cmp_ok(marc_leader('11',5), '==', 5, 'marc_leader');
677         ok(marc_leader(), 'marc_leader get');
678         diag "leader: ", dump(marc_leader()) if ($debug);
679         is_deeply(marc_leader(), { '06' => 42, 11 => 5 }, "marc_leader full");
680
681         test_rule(
682                 'rec1(000)',
683                 { '000' => [ 42 ]},
684                 qq{ rec('000') },
685                 [ 42 ],
686         );
687
688         test_rec_rules(
689                 'marc(001,rec(000))',
690                 { '000' => [ 42 ]},
691                 qq{
692                         marc('001', rec('000') );
693                 },
694                 [
695                         [ '001', 42, ]
696                 ],
697         );
698
699         test_rec_rules(
700                 'marc_remove subfield',
701                 { '200' => [{ a => 42, b => 'bar', c => 'baz' }] },
702                 qq{
703                         marc('900', 'a', rec('200','a') );
704                         marc('900', 'b', rec('200','b') );
705                         marc_remove('900','b');
706                         marc('900', 'b', rec('200','c') );
707                         marc_remove('900','a');
708                 },
709                 [
710                         [ '900', ' ', ' ', 'b', 'baz' ],
711                 ],
712         );
713
714         test_rec_rules(
715                 'marc_remove field',
716                 { '200' => [{ a => 42, b => 'bar', c => 'baz' }] },
717                 qq{
718                         marc('900', 'a', rec('200','a') );
719                         marc('900', 'b', rec('200','b') );
720                         marc('901', 'b', rec('200','b') );
721                         marc('901', 'c', rec('200','c') );
722                         marc_remove('900');
723                 },
724                 [
725                         [ '901', ' ', ' ', 'b', 'bar', 'c', 'baz' ],
726                 ],
727         );
728         test_rec_rules(
729                 'marc_duplicate',
730                 { '200' => [{ a => 42, b => 'bar', c => 'baz', d => 'bing', e => 'bong' }] },
731                 qq{
732                         marc('900', 'a', rec('200','a') );
733                         marc('900', 'b', rec('200','b') );
734                         marc_duplicate;
735                         marc_remove('900','b');
736                         marc('900', 'b', rec('200','c') );
737                         marc_duplicate;
738                         marc_remove('900','b');
739                         marc('900', 'b', rec('200','d') );
740                         marc_duplicate;
741                         marc_remove('900','b');
742                         marc('900', 'b', rec('200','e') );
743                 },
744                 [
745                         # this will return FIRST record
746                         [ '900', ' ', ' ', 'a', 42, 'b', 'bar' ],
747                 ],
748         );
749
750         my $i = 0;
751         foreach my $v ( qw/bar baz bing bong/ ) {
752
753                 ok($marc = WebPAC::Normalize::_get_marc_fields( offset => $i ),
754                         "_get_marc_fields( offset => $i )"
755                 );
756                 diag "marc $i = ", dump( $marc ) if ($debug);
757                 is_deeply( $marc,
758                         [ [ '900', ' ', ' ', 'a', 42, 'b', $v ] ],
759                         "MARC copy $i has $v",
760                 );
761                 $i++;
762         }
763
764         test_rec_rules(
765                 'marc_original_order',
766                 {
767                         '200' => [ {
768                                 a => [ 'a1', 'a2' ], b => [ 'b1', 'b2' ], c => [ 'c1', 'c2' ],
769                                 subfields => [ qw/a 0 b 0 a 1 b 1 c 0 c 1/ ],
770                         }, {
771                                 a => [ 'a3', 'a4', 'a5' ], b => 'b3', c => 'c3',
772                                 subfields => [ qw/a 0 a 1 b 0 c 0 a 2/ ],
773                         } ],
774                 },
775                 qq{
776                         marc_original_order(900,200);
777                 },
778                 [
779                         [ '900', ' ', ' ', 'a', 'a1', 'b', 'b1', 'a', 'a2', 'b', 'b2', 'c', 'c1', 'c', 'c2', ],
780                         [ '900', ' ', ' ', 'a', 'a3', 'a', 'a4', 'b', 'b3', 'c', 'c3', 'a', 'a5', ],
781                 ],
782         );
783
784         test_rule(
785                 'rec1 skips subfields',
786                 {
787                         '200' => [ {
788                                 a => [ 'a1', 'a2' ], b => [ 'b1', 'b2' ], c => [ 'c1', 'c2' ],
789                                 subfields => [ qw/a 0 b 0 a 1 b 1 c 0 c 1/ ],
790                         }, {
791                                 a => [ 'a3', 'a4', 'a5' ], b => 'b3', c => 'c3',
792                                 subfields => [ qw/a 0 a 1 b 0 c 0 a 2/ ],
793                         } ],
794                 },
795                 qq{
796                         rec1(200);
797                 },
798                 ['a1', 'b1', 'a2', 'b2', 'c1', 'c2', 'a3', 'a4', 'b3', 'c3', 'a5' ],
799         );
800
801         is_deeply(
802                 [ _pack_subfields_hash({
803                         a => [ 'a1', 'a2' ], b => [ 'b1', 'b2' ], c => [ 'c1', 'c2' ],
804                         subfields => [ qw/a 0 b 0 a 1 b 1 c 0 c 1/ ],
805                 }) ],
806                 ['a1', 'b1', 'a2', 'b2', 'c1', 'c2'],
807                 '_pack_subfields_hash( $h )'
808         );
809
810         cmp_ok(
811                 _pack_subfields_hash({
812                         a => [ 'a1', 'a2' ], b => [ 'b1', 'b2' ], c => [ 'c1', 'c2' ],
813                         subfields => [ qw/a 0 b 0 a 1 b 1 c 0 c 1/ ],
814                 }, 1),
815                 'eq',
816                 '^aa1^bb1^aa2^bb2^cc1^cc2',
817                 '_pack_subfields_hash( $h, 1 )'
818         );
819 }
820