root/branches/release-40/t/driver-tests.pl @ 2601

Revision 2601, 29.5 kB (checked in by fumiakiy, 18 months ago)

Fix for db driver tests

  • Drop sequences before creating new ones.
  • Specify IDs to new objects for later comparison.
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#!/usr/bin/perl
2
3# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
4# This program is distributed under the terms of the
5# GNU General Public License, version 2.
6#
7# $Id$
8
9use strict;
10use warnings;
11use Data::Dumper;
12use English qw( -no_match_vars );
13
14$OUTPUT_AUTOFLUSH = 1;
15
16# Run this script as a symlink, in the form of 99-driver.t, ie:
17# ln -s driver-tests.pl 99-driver.t
18
19BEGIN {
20    # Set config to driver-test.cfg when run as /path/to/99-driver.t
21    $ENV{MT_CONFIG} = "$1-test.cfg"
22        if __FILE__ =~ m{ [\\/] \d+- ([^\\/]+) \.t \z }xms;
23}
24
25use Test::More;
26use lib 't/lib';
27
28BEGIN {
29    plan skip_all => "Configuration file $ENV{MT_CONFIG} not found"
30        if !-r "t/$ENV{MT_CONFIG}";
31}
32
33use MT::Test qw(:testdb :time);
34
35
36package Zot;
37use base 'MT::Object';
38__PACKAGE__->install_properties({
39    column_defs => {
40        'id' => 'integer not null auto_increment',
41        'x' => 'string(255)',
42    },
43    primary_key => 'id',
44    datasource => 'zot',
45});
46
47
48package Test::GroupBy;
49use base qw( Test::Class MT::Test );
50use Test::More;
51use POSIX qw(strftime);
52
53sub reset_db : Test(setup) {
54    my $self = shift;
55    $self->clean_db();
56
57    my @obj_data = (
58        { class => 'Foo',
59          id => 1,
60          name => 'foo',
61          text => 'bar',
62          status => 2, },
63        { class => 'Foo',
64          id => 2,
65          name => 'baz',
66          text => 'quux',
67          status => 1, },
68        { class => 'Bar',
69          id => 1,
70          foo_id => 2,
71          name => 'bar0',
72          status => 0, },
73        { class => 'Bar',
74          id => 2,
75          foo_id => 2,
76          name => 'bar1',
77          status => 1, },
78        { class => 'Bar',
79          id => 3,
80          foo_id => 1,
81          name => 'bar2',
82          status => 0, },
83    );
84
85    for my $data (@obj_data) {
86        my $class = delete $data->{class};
87        my $obj = $class->new;
88        $obj->set_values($data);
89        $obj->save();
90    }
91}
92
93sub count_group_by : Tests(26) {
94    # legacy way of specifying sort direction
95    my $cgb_iter = Bar->count_group_by({
96            status => '0',
97        }, {
98            group => [ 'foo_id' ],
99            sort => 'foo_id desc',
100        });
101    my ($count, $bfid, $month);
102    isa_ok($cgb_iter, 'CODE');
103    ok(($count, $bfid) = $cgb_iter->(), 'set');
104    is($bfid, 2, 'id');
105    is($count, 1, 'count4');
106    ok(($count, $bfid) = $cgb_iter->(), 'set');
107    is($bfid, 1, 'id');
108    is($count, 1, 'count5');
109    ok(!$cgb_iter->(), 'no $iter');
110
111    # new way of specifying sort direction
112    my $cgb_iter2 = Bar->count_group_by({
113            status => '0',
114        }, {
115            group => [ 'foo_id' ],
116            sort => 'foo_id',
117            direction => 'descend'
118        });
119
120    isa_ok($cgb_iter2, 'CODE');
121    ok(($count, $bfid) = $cgb_iter2->(), 'set');
122    is($bfid, 2, 'id');
123    is($count, 1, 'count4');
124    ok(($count, $bfid) = $cgb_iter2->(), 'set');
125    is($bfid, 1, 'id');
126    is($count, 1, 'count5');
127    ok(!$cgb_iter2->(), 'no $iter');
128
129    # legacy way of specifying sort direction
130    my $cgb_iter3 = Bar->count_group_by(undef, {
131            group => [ 'extract(month from created_on)' ],
132            sort => 'extract(month from created_on) desc',
133        });
134    isa_ok($cgb_iter3, 'CODE');
135    ok(($count, $month) = $cgb_iter3->(), 'set');
136    is(int($month), int(strftime("%m", localtime)), 'month');
137    is($count, 3, 'count6');
138    ok(!$cgb_iter3->(), 'no $iter');
139
140    # new way of specifying sort direction
141    my $cgb_iter4 = Bar->count_group_by(undef, {
142            group => [ 'extract(month from created_on)' ],
143            sort => [{ column => 'extract(month from created_on)',
144                desc => 'desc' }]
145        });
146    isa_ok($cgb_iter4, 'CODE');
147    ok(($count, $month) = $cgb_iter4->(), 'set');
148    is(int($month), int(strftime("%m", localtime)), 'month');
149    is($count, 3, 'count6');
150    ok(!$cgb_iter4->(), 'no $iter');
151}
152
153sub sum_group_by : Tests(7) {
154    # Sum status values across groups of ids (that is, a group for each Foo).
155    my $sgb = Foo->sum_group_by(undef, {
156        sum       => 'status',
157        group     => ['id'],
158        direction => 'ascend',
159    });
160
161    my ($status, $id) = $sgb->();
162    ok($status && $id, 'sum_group_by results had a first result');
163    is($status, 1, q{sum_group_by result #1's status is 1});
164    is($id, 2, 'sum_group_by result #1 was for Foo #2');
165   
166    ($status, $id) = $sgb->();
167    ok($status && $id, 'sum_group_by results had a second result');
168    is($status, 2, q{sum_group_by result #2's status is 2});
169    is($id, 1, 'sum_group_by result #2 was for Foo #1');
170   
171    ($status, $id) = $sgb->();
172    ok(!$status, 'sum_group_by only had two results');
173}
174
175sub avg_group_by : Tests(7) {
176    my $agb = Foo->avg_group_by(undef, {
177        avg => 'status',
178        group => ['id'],
179        direction => 'ascend',
180    });
181   
182    my ($status, $id) = $agb->();
183    ok($status && $id, 'avg_group_by results had a first result');
184    # Compare numerically; is() will compare stringwise.
185    ok($status == 1, q{avg_group_by result #1's status is 1});
186    is($id, 2, 'avg_group_by result #1 was for Foo #2');
187   
188    ($status, $id) = $agb->();
189    ok($status && $id, 'avg_group_by results had a second result');
190    # Compare numerically; is() will compare stringwise.
191    ok($status == 2, q{avg_group_by result #2's status is 2});
192    is($id, 1, 'avg_group_by result #2 was for Foo #1');
193   
194    ($status, $id) = $agb->();
195    ok(!$status, 'avg_group_by only had two results');
196}
197
198sub clean_db : Test(teardown) {
199    MT::Test->reset_table_for(qw( Foo Bar ));
200}
201
202
203package Test::Search;
204use Test::More;
205use MT::Test;
206use base qw( Test::Class MT::Test );
207
208sub reset_db : Test(setup) {
209    MT::Test->reset_table_for(qw( Foo Bar ));
210}
211
212sub make_basic_data {
213    my $self = shift;
214    $self->make_objects(
215        { __class  => 'Foo',
216          __wait   => 1,
217          name     => 'foo',
218          text     => 'bar',
219          id => 1,
220          status   => 2,     },
221        { __class  => 'Foo',
222          __wait   => 3,
223          name     => 'baz',
224          text     => 'quux',
225          id => 2,
226          status   => 1,      },
227    );
228}
229
230sub make_pc_data {
231    my $self = shift;
232    $self->make_objects(
233        { __class => 'Foo',
234          name    => 'Apple',
235          text    => 'MacBook',
236          status  => 11,        },
237        { __class => 'Foo',
238          name    => 'Linux',
239          text    => 'Ubuntu',
240          status  => 12,       },
241        { __class => 'Foo',
242          name    => 'Microsoft',
243          text    => 'Vista',
244          status  => 13,          },
245        { __class => 'Foo',
246          name    => 'Microsoft',
247          text    => 'XP',
248          status  => 10,          },
249        { __class => 'Foo',
250          name    => 'Apple',
251          text    => 'iBook',
252          status  => 10,      },
253
254        { __class => 'Bar',
255          name    => 'Silverlight',
256          status  => 2,
257          foo_id  => 3,             },
258        { __class => 'Bar',
259          name    => 'IronPython',
260          status  => 3,
261          foo_id  => 3,            },
262        { __class => 'Bar',
263          name    => 'IronRuby',
264          status  => 0,
265          foo_id  => 1,          },
266    );
267}
268
269sub basic : Tests(5) {
270    my $self = shift;
271    $self->make_basic_data();
272
273    my $foo = Foo->load(1);  # not a search
274
275    is_object(scalar Foo->load({ id => 1 }), $foo, 'Foo #1 by id hash is Foo #1');
276    is_object(scalar Foo->load({ id => 1, name => 'foo' }), $foo, 'Foo #1 by id-name hash is Foo #1');
277    is_object(scalar Foo->load({ name => 'foo' }), $foo, 'Foo #1 by name hash is Foo #1');
278    is_object(scalar Foo->load({ created_on => $foo->created_on }), $foo, 'Foo #1 by created_on hash is Foo #1');
279    is_object(scalar Foo->load({ status => 2 }), $foo, 'Foo #1 by status hash is Foo #1');
280}
281
282sub sorting : Tests(6) {
283    my $self = shift;
284    $self->make_basic_data();
285   
286    my ($tmp, @tmp);
287    my @foo = map { Foo->load($_) } (1..2);
288
289    ## Load using descending sort (newest)
290    $tmp = Foo->load(undef, {
291        sort => 'created_on',
292        direction => 'descend',
293        limit => 1 });
294    is_object($tmp, $foo[1], 'Newest Foo is Foo #2');
295
296    ## Load using ascending sort (oldest)
297    $tmp = Foo->load(undef, {
298        sort => 'created_on',
299        direction => 'ascend',
300        limit => 1 });
301    is_object($tmp, $foo[0], 'Oldest Foo is Foo #1');
302
303    ## Load using descending sort with limit = 2
304    @tmp = Foo->load(undef, {
305        sort => 'created_on',
306        direction => 'descend',
307        limit => 2 });
308    are_objects(\@tmp, [ reverse @foo ], 'Two Foos newest-first load() finds Foos #2 and #1');
309
310    ## Load using descending sort by created_on, no limit
311    @tmp = Foo->load(undef, {
312        sort => 'created_on',
313        direction => 'descend' });
314    are_objects(\@tmp, [ reverse @foo ], 'All Foos newest-first load() finds Foos #2 and #1');
315
316    ## Load using ascending sort by status, no limit
317    @tmp = Foo->load(undef, { sort => 'status', });
318    are_objects(\@tmp, [ reverse @foo ], 'All Foos lowest-status-first load() finds Foos #2 and #1');
319
320    ## Load using 'last' where status == 2
321    $tmp = Foo->load({ status => 2 }, {
322        sort => 'created_on',
323        direction => 'descend',
324        limit => 1 });
325    is_object($tmp, $foo[0], 'Newest status=2 Foo is Foo #1');
326}
327
328sub ranges : Tests(9) {
329    my $self = shift;
330    $self->make_basic_data();
331
332    my $tmp;
333    my @foo = map { Foo->load($_) } (1..2);
334   
335    ## Load using range search, one less than foo[1]->created_on and newer
336    $tmp = Foo->load(
337        { created_on => [ $foo[1]->column('created_on')-1 ] },
338        { range => { created_on => 1 } });
339    is_object($tmp, $foo[1], 'Foo from open-ended date range before Foo #2 is Foo #2');
340
341    ## Load using EXCLUSIVE range search, up through the momment $foo[1] created
342    $tmp = Foo->load(
343        { created_on => [ $foo[1]->column('created_on')-1, 
344                          $foo[1]->column('created_on') ] },
345        { range => { created_on => 1 } });
346    ok(!$tmp, "Exclusive date range load() ending at Foo #1's date found no Foos");
347
348    $tmp = Foo->load(
349        { created_on => [ $foo[1]->column('created_on'), 
350                          $foo[1]->column('created_on')+1 ] },
351        { range => { created_on => 1 } });
352    ok(!$tmp, "Exclusive date range load() starting at Foo #1's date found no Foos");
353
354    ## Load using INCLUSIVE range search, up through the momment $foo[1] created
355    $tmp = Foo->load(
356        { created_on => [ $foo[1]->column('created_on')-1, 
357                          $foo[1]->column('created_on') ] },
358        { range_incl => { created_on => 1 } });
359    ok($tmp, 'Loaded an object based on range_incl (ts-1 to ts)');
360    is_object($tmp, $foo[1], "Foo from inclusive date-range load() ending at Foo #1's date is Foo #2");
361
362    $tmp = Foo->load(
363        { created_on => [ $foo[1]->column('created_on'), 
364                          $foo[1]->column('created_on')+1 ] },
365        { range_incl => { created_on => 1 } });
366    ok($tmp, 'Loaded an object based on range_incl (ts to ts+1)');
367    is_object($tmp, $foo[1], "Foo from inclusive date-range load() starting at Foo #1's date is Foo #2");
368
369    ## Check that range searches return nothing when nothing is in the range.
370    $tmp = Foo->load( { created_on => [ undef, '19690101000000' ] },
371                  { range => { created_on => 1 } });
372    ok(!$tmp, 'Prehistoric date range load() found no Foos');
373
374    ## Range search, all items with created_on less than foo[1]->created_on
375    $tmp = Foo->load(
376        { created_on => [ undef, $foo[1]->column('created_on')-1 ] },
377        { range => { created_on => 1 } });
378    is_object($tmp, $foo[0], "Foo from exclusive open-started date-range load() ending before Foo #1 is Foo #1");
379}
380
381sub alias : Tests(2) {
382    my $self = shift;
383    $self->make_pc_data();
384
385    my $vista = Foo->load(3);  # not a search
386
387    # select * from foo, bar bar1, bar bar2
388    # where bar1.bar_foo_id = foo_id
389    # and bar2.bar_foo_id = bar1.bar_foo_id
390    # and bar1.status = 2
391    # and bar2.status = 3
392    my @a_foos = Foo->load(
393        undef,
394        { join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 2 },
395            { join => [ 'Bar', undef, { foo_id => \'= bar1.bar_foo_id', status => 3 },
396                { alias => 'bar2' } ],
397              alias => 'bar1'
398            }
399          ],
400          sort => 'created_on', direction => 'descend',
401        }
402    );
403    are_objects(\@a_foos, [ $vista ], 'Has Bars with status=2 and status=3 (alias)');
404
405    @a_foos = Foo->load(
406        undef,
407        { join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 2 },
408            { join => [ 'Bar', undef, { foo_id => \'= bar1.bar_foo_id', status => 0 },
409                { alias => 'bar2' } ],
410              alias => 'bar1'
411            }
412          ],
413          sort => 'created_on', direction => 'descend',
414        }
415    );
416    is_deeply(\@a_foos, [], 'No Foo has Bars with status=2 and status=0 (alias)');
417} 
418
419sub conjunctions : Tests(4) {
420    my $self = shift;
421    $self->make_pc_data();
422    my @foos = map { Foo->load($_) } (1..5);  # not a search
423
424    my @res = Foo->load([
425        { status => 10 },
426        -or => { name => 'Apple' },
427    ]);
428    @res = sort { $a->id <=> $b->id } @res;
429    are_objects(\@res, [ @foos[0,3,4] ], '-or results');
430
431    @res = Foo->load([
432        { name => 'Microsoft' },
433        -and => { status => 10 },
434    ]);
435    are_objects(\@res, [ $foos[3] ], '-and results');
436
437    @res = Foo->load([
438        { status => { '<=' => 20 },
439          name => 'Apple' },
440        -and_not => { status => 11 },
441    ]);
442    # where (foo_status <= 20 and foo_name = 'Apple') and not (foo_status = 11)
443    are_objects(\@res, [ $foos[4] ], '-and_not results');
444
445    @res = Foo->load([
446        { status => 10 },
447        -or => { name => 'Apple' },
448        -or => { name => { like => '%nux' } },
449    ]);
450    @res = sort { $a->id <=> $b->id } @res;
451    # where (foo_status = 10) or (foo_name = 'Apple') or (foo_name like '%nux')
452    # (selects Apple+MacBook, Apple+iBook, Microsoft+XP, Linux+Ubuntu)
453    are_objects(\@res, [ @foos[0,1,3,4] ], 'big -or results');
454}
455
456sub clean_db : Test(teardown) {
457    MT::Test->reset_table_for(qw( Foo Bar ));
458}
459
460
461package main;
462use MT::Test;
463
464Test::Class->runtests('Test::GroupBy', 'Test::Search', +126);
465
466my($foo, @foo, @bar);
467my($tmp, @tmp);
468
469# Test for existing table
470ok(MT::Object->driver->dbd->ddl_class->column_defs('Foo'), "table mt_foo exists after upgrade");
471# Test for non-existent table
472ok(!MT::Object->driver->dbd->ddl_class->column_defs('Zot'), "table mt_zot does not exist after upgrade where undefined");
473
474## Test creating object with new
475##     test column access through column, then through AUTOLOAD
476$foo = Foo->new;
477isa_ok($foo, 'Foo', 'New Foo could be created');
478$foo->column('name', 'foo');
479is($foo->column('name'), 'foo', 'Setting name field with column() persists through access');
480$foo->name('foo');
481is($foo->name, 'foo', 'Setting name field with mutator method persists through access');
482$foo->status(2);
483$foo->text('bar');
484
485## Test saving created object
486ok($foo->save, 'A Foo could be saved');
487is($foo->id, 1, 'First Foo was given an id of 1, says accessor method');
488is($foo->column('id'), 1, 'First Foo was given an id of 1, says column()');
489
490is_object(scalar Foo->load(1), $foo, 'Foo #1 by id is Foo #1');
491
492##     Change column value, save, try to load using old value (fail?),
493##     then load again using new value
494$foo->status(0);
495ok($foo->save, 'Foo #1 saved with new status (0)');
496$tmp = Foo->load({ status => 2 });
497ok(!$tmp, 'Foo #1 no longer loads with old status (2)');
498$tmp = Foo->load({ status => 0 });
499is_object($tmp, $foo, 'Foo #1 by new status (0) is Foo #1');
500
501## Create a new object so we can do range and last/first lookups.
502## Sleep first so that they get different created_on timestamps.
503sleep(3);
504
505## Create new object for iterator testing
506$foo[0] = $foo;
507$foo[1] = Foo->new;
508$foo[1]->name('baz');
509$foo[1]->text('quux');
510$foo[1]->status(1);
511$foo[1]->save;
512
513## TEST LOADING IN VARIOUS WAYS
514
515## Load all objects via iterator
516my $iter = Foo->load_iter(undef, { sort => 'created_on', direction => 'ascend' });
517isa_ok($iter, 'CODE', "Iterator for all Foos");
518ok($tmp = $iter->(), 'Iterator for our two Foos had one object');
519is_object($tmp, $foo[0], "All Foo iterator's first Foo is Foo #1");
520ok($tmp = $iter->(), 'Iterator for our two Foos had two objects');
521is_object($tmp, $foo[1], "All Foo iterator's second Foo is Foo #2");
522ok(!$iter->(), 'Iterator for our two Foos did not have a third object');
523
524## Load all objects with status == 1 via iterator
525$iter = Foo->load_iter({ status => 1 });
526isa_ok($iter, 'CODE', "Iterator for status=1 Foos");
527ok($tmp = $iter->(), 'Iterator for our status=1 Foos had one object');
528is_object($tmp, $foo[1], "Status=1 Foo iterator's first Foo is Foo #2");
529ok(!$iter->(), "Iterator for our status=1 Foos did not have a second object");
530
531## Load using non-existent ID (should fail)
532$tmp = Foo->load(3);
533ok(!$tmp, 'There is no Foo #3');
534
535
536## Get count of objects
537is(Foo->count(), 2, 'Count of all Foos finds both');
538is(Foo->count({ status => 0 }), 1, 'Count of all status=0 Foos finds all one');
539my $ranged_count = Foo->count(
540    { created_on => [ $foo[1]->column('created_on')-1 ] },
541    { range => { created_on => 1 } }
542);
543is($ranged_count, 1, 'Count of all Foos in open-ended date range starting before Foo #1 finds all one');
544
545## Update status for later tests.
546$foo[0]->status(2);
547$foo[0]->save;
548
549## Test start_val loads.
550## Given the first Foo object, should load the "next" one
551## (the one with a larger created_on time)
552$tmp = Foo->load(undef, {
553    limit => 1,
554    sort => 'created_on',
555    direction => 'ascend',
556    start_val => $foo[0]->created_on });
557is_object($tmp, $foo[1], 'Next newer Foo after Foo #1 is Foo #2');
558
559## Given the first Foo object, try to load the "previous" one
560## (the one with a smaller created_on time). This should fail.
561$tmp = Foo->load(undef, {
562    limit => 1,
563    sort => 'created_on',
564    direction => 'descend',
565    start_val => $foo[0]->created_on });
566ok(!$tmp, 'Search for next older Foo before Foo #1 found none');
567
568## Given the second Foo object, try to load the "previous" one
569## (the one with a smaller created_on time). This should work.
570$tmp = Foo->load(undef, {
571    limit => 1,
572    sort => 'created_on',
573    direction => 'descend',
574    start_val => $foo[1]->created_on });
575is_object($tmp, $foo[0], 'Next older Foo before Foo #2 is Foo #1');
576
577## Given the second Foo object, try to load the "next" one
578## (the one with a larger created_on time). This should fail.
579$tmp = Foo->load(undef, {
580    limit => 1,
581    sort => 'created_on',
582    direction => 'ascend',
583    start_val => $foo[1]->created_on });
584ok(!$tmp, 'Search for next newer Foo after Foo #2 found none');
585
586## Now, given the second Foo object's created_on - 1, try to
587## load the "previous" one. This should work.
588$tmp = Foo->load(undef, {
589    limit => 1,
590    sort => 'created_on',
591    direction => 'descend',
592    start_val => $foo[1]->created_on-1 });
593is_object($tmp, $foo[0], 'Next older Foo before just before Foo #2 is Foo #1');
594
595## Now, given the second Foo object's created_on - 1, try to
596## load the "next" one. This should work.
597$tmp = Foo->load(undef, {
598    limit => 1,
599    sort => 'created_on',
600    direction => 'ascend',
601    start_val => $foo[1]->created_on-1 });
602is_object($tmp, $foo[1], 'Next newer Foo after just before Foo #2 is Foo #2');
603
604## Override created_on timestamp, make sure it works
605my $ts = substr($foo[1]->created_on, 0, -4) . '0000';
606$foo[1]->created_on($ts);
607$foo[1]->save;
608
609@tmp = Foo->load(undef, {
610    sort => 'created_on',
611    direction => 'descend',
612    limit => 2 });
613are_objects(\@tmp, \@foo, 'Time-traveled Foos newest-first are Foos #1 and #2');
614
615## Test limit of 2 with direction descend, but without
616## a sort option. This should sort by the most recently-added
617## records, ie. sorted by ID, basically.
618@tmp = Foo->load(undef, {
619    direction => 'descend',
620    limit => 2 });
621are_objects(\@tmp, [ reverse @foo ], 'Foos highest-id-first are Foos #2 and #1');
622
623## Test loading using offset.
624## Should load the second Foo object.
625$tmp = Foo->load(undef, {
626    direction => 'descend',
627    sort => 'created_on',
628    limit => 1,
629    offset => 1 });
630is_object($tmp, $foo[1], 'Second newest Foo is Foo #2');
631
632## We only have 2 Foo objects, so this should load
633## only the second Foo object (because offset is 1).
634@tmp = Foo->load(undef, {
635    direction => 'descend',
636    sort => 'created_on',
637    limit => 2,
638    offset => 1 });
639are_objects(\@tmp, [ $foo[1] ], 'Second and third newest Foos is just Foo #2');
640
641## Should load the first Foo object (ascend with offset of 1).
642$tmp = Foo->load(undef, {
643    direction => 'ascend',
644    sort => 'created_on',
645    limit => 1,
646    offset => 1 });
647is_object($tmp, $foo[0], 'Second oldest Foo is Foo #1');
648
649## Now test join loads.
650## First we need to create a couple of Bar objects.
651$bar[0] = Bar->new;
652$bar[0]->foo_id($foo[1]->id);
653$bar[0]->name('bar0');
654$bar[0]->status(0);
655ok($bar[0]->save, 'saved');
656sleep(2);  ## Sleep to ensure created_on timestamps are unique
657
658$bar[1] = Bar->new;
659$bar[1]->foo_id($foo[1]->id);
660$bar[1]->name('bar1');
661$bar[1]->status(1);
662ok($bar[1]->save, 'saved');
663sleep(2);  ## Sleep to ensure created_on timestamps are unique
664
665$bar[2] = Bar->new;
666$bar[2]->foo_id($foo[0]->id);
667$bar[2]->name('bar2');
668$bar[2]->status(0);
669ok($bar[2]->save, 'saved');
670sleep(2);  ## Sleep to ensure created_on timestamps are unique
671
672## Get a count of all Foo objects in order of most recently
673## created Bar object. No uniqueness requirement. This tests
674## the on_load_complete temporary table stuff with count.
675
676is(Foo->count(undef,
677    { join => [ 'Bar', 'foo_id',
678                undef,
679                { unique => 1,
680                  sort => 'created_on',
681                  direction => 'descend', } ] }), 2, 'There are 2 unique Foos associated with Bars');
682
683## Now load all Foo objects in order of most recently
684## created Bar object. Make sure they are unique.
685@tmp = Foo->load(undef,
686    { join => [ 'Bar', 'foo_id',
687                undef,
688                { sort => 'created_on',
689                  direction => 'descend',
690                  unique => 1 } ] });
691are_objects(\@tmp, \@foo, 'unique Foos associated with Bars, oldest first');
692
693## Load all Foo objects in order of most recently
694## created Bar object. No uniqueness requirement.
695@tmp = Foo->load(undef,
696    { join => [ 'Bar', 'foo_id',
697                undef,
698                { sort => 'created_on',
699                  direction => 'descend', } ] });
700are_objects(\@tmp, [ @foo, $foo[1] ], 'Foos associated with Bars, oldest first');
701
702## Load last 1 Foo object in order of most recently
703## created Bar object.
704@tmp = Foo->load(undef,
705    { join => [ 'Bar', 'foo_id',
706                undef,
707                { sort => 'created_on',
708                  direction => 'descend',
709                  unique => 1,
710                  limit => 1, } ] });
711are_objects(\@tmp, [ $foo[0] ], 'Foos associated with oldest Bar');
712
713## Load all Foo objects where Bar.name = 'bar0'
714@tmp = Foo->load(undef,
715    { join => [ 'Bar', 'foo_id',
716                { name => 'bar0' },
717                { sort => 'created_on',
718                  direction => 'descend',
719                  unique => 1, } ] });
720are_objects(\@tmp, [ $foo[1] ], 'Foos associated with Bars named bar0');
721
722## foo[1] is older than foo[0] because we overrode the timestamp,
723## so this should load foo[0]
724@tmp = Foo->load(undef,
725    { sort => 'created_on', direction => 'descend', limit => 1,
726    join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
727are_objects(\@tmp, [ $foo[0] ], 'One Foo associated with Bars of status=0');
728
729## This is the same join as the last one, but without the limit--so
730## we should get both Foo objects this time, in descending order.
731@tmp = Foo->load(undef,
732    { sort => 'created_on', direction => 'descend',
733      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
734are_objects(\@tmp, \@foo, 'All Foos associated with Bars of status=0');
735
736## Filter join results by providing a value for 'status'; only Foo[0]
737## has a 'status' == 2, so only that record should be returned.
738@tmp = Foo->load({ status => 2 },
739    { sort => 'created_on', direction => 'descend',
740      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
741are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 associated with Bars of status=0');
742
743# Join across a column.
744@tmp = Foo->load({},
745    { sort => 'created_on', direction => 'descend',
746      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
747are_objects(\@tmp, \@foo, 'Foos loaded by explicit join across columns');
748
749@tmp = Foo->load({ status => 2 },
750    { sort => 'created_on', direction => 'descend',
751      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
752are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 loaded by explicit join across columns');
753
754## TEST EXISTS METHOD
755ok($foo->exists, 'First Foo long saved exists in db');
756$tmp = Foo->new;
757ok(!$tmp->exists, 'New Foo just created does not exist in db');
758$tmp->id(5);
759ok(!$tmp->exists, 'New Foo just created with fake id does not exist in db');
760
761## Change foo[1]->status so that its value is unique (for index)
762$foo[1]->status(5);
763ok($foo[1]->save, 'saved');
764ok(Foo->load({ status => 5 }), 'loaded' );
765
766## Test remove
767ok($foo[1]->remove, 'removed');
768ok(! Foo->load(2), 'not loaded');
769ok(! Foo->load({ status => 5 }), 'not loaded');
770ok(! Foo->load({ name => $foo[1]->name }), 'not loaded');
771ok(! Foo->load({ created_on => $foo[1]->created_on }), 'not loaded');
772
773## Test methods:
774##     * properties
775my $props1 = Foo->properties;
776is($props1->{audit}, 1, 'audit');
777is(scalar keys %{ $props1->{indexes} }, 3, 'indexes');
778is($props1->{primary_key}, 'id', 'id');
779is(scalar @{ $props1->{columns} }, 9, 'columns');
780my $props2 = $foo->properties;
781is($props1, $props2, "$props1 is $props2");  ## Same address, because same hashref
782
783##     * column_names
784my $cols = $foo->column_names;
785isa_ok($cols, 'ARRAY');
786my %cols = map { $_ => 1 } @$cols;
787for (qw(id name status text data created_on created_by modified_on modified_by)) {
788    ok($cols{$_}, 'cols');
789}
790
791##     * column_values
792my $vals = $foo->column_values;
793isa_ok($vals, 'HASH');
794is($vals->{id}, $foo->id, 'id');
795is($vals->{name}, $foo->name, 'name');
796is($vals->{status}, $foo->status, 'status');
797is($vals->{text}, $foo->text, 'text');
798is($vals->{created_on}, $foo->created_on, 'created_on');
799is($vals->{created_by}, $foo->created_by, 'created_by');
800is($vals->{modified_on}, $foo->modified_on, 'modified_on');
801is($vals->{modified_by}, $foo->modified_by, 'modified_by');
802
803##     * set_values
804$vals = {
805    id => 5,
806    name => 'baz',
807    status => 7,
808    text => 'quux',
809    created_on => 13209,
810    created_by => 'bar',
811    #modified_on => 39023, modified_by auto-set modified_on in our new code.
812    modified_by => 'foo',
813};
814$foo->set_values($vals);
815for my $col (keys %$vals) {
816    is($vals->{$col}, $foo->column($col), $col);
817}
818
819##     * binary data
820
821my $binmonster = Foo->new;
822
823$vals = {
824    funky => "yes",
825    monkey => "no",
826};
827
828require MT::Serialize;
829my $srlzr = MT::Serialize->new('MT');
830$binmonster->data($srlzr->serialize(\$vals));
831my $x = $binmonster->save();
832warn 'Failed binary data test: ' . $binmonster->errstr() unless $x;
833ok($x, 'saved');
834ok($binmonster->id, 'id');
835Foo->driver->clear_cache if Foo->driver->can('clear_cache');
836my $chk = Foo->load($binmonster->id);
837if ($chk) {
838    my $chk_data = $chk->data;
839    my $chk_vals = $srlzr->unserialize($chk_data);
840    foreach (keys %$vals) {
841        is($$chk_vals->{$_}, $vals->{$_}, $_);
842    }
843} else {
844    foreach (keys %$vals) {
845        ok(0, $_);
846    }
847}
848
849##     * datasource
850is($foo->table_name, 'mt_' . $foo->datasource, 'datasource');
851
852##     * clone
853my $clone = $foo->clone_all;
854for my $col (@$cols) {
855    is($clone->column($col), $foo->column($col), $col);
856}
857
858## Sleep first so that they get different created_on timestamps.
859sleep(3);
860
861Foo->set_by_key({name => "this"});
862my $obj = Foo->load({name => "this"});
863isa_ok($obj, 'Foo');
864
865Foo->set_by_key({name => "this"}, {status => 42});
866$obj = Foo->load({name => "this"});
867is($obj && $obj->status, 42, 'status');
868
869Foo->set_by_key({name => "this"}, {status => 47});
870$obj = Foo->load({name => "this"});
871is($obj && $obj->status, 47, 'status');
872
873Foo->set_by_key({name => "this", status => 47}, {text => "spiffy"});
874$obj = Foo->load({name => "this", status => 47});
875is($obj && $obj->text, 'spiffy', 'text');
876
877sleep(3);
878
879Foo->set_by_key({name => "that"}, {text => "Once"});
880$obj = Foo->load({name => "that"});
881is($obj && $obj->text, 'Once', 'text');
882
883Foo->driver->clear_cache if Foo->driver->can('clear_cache');
884## Load use direct set of values for non-PK column
885@tmp = Foo->load({ name => [qw(foo this)] });
886@tmp = sort {$a->name cmp $b->name} @tmp;
887is(@tmp, 2, 'array length 2');
888
889is(Foo->count(), 4, 'check number of Foos');
890
891## check offsets without limits
892## Should load the third and fourth Foo objects.
893my $foo4 = Foo->load({name => "this"});
894my $foo5 = Foo->load({name => "that"});
895my $foo1 = Foo->load(undef, { 'sort' => 'created_on', 'direction' => 'ascend' });
896my @offs = Foo->load(undef, {
897    direction => 'ascend',
898    sort => 'created_on',
899    offset => 2 });
900is(@offs, 2, 'array length 2');
901isa_ok($offs[0], 'Foo');
902is($offs[0]->id, $foo4->id, 'id');
903isa_ok($offs[1], 'Foo');
904is($offs[1]->id, $foo5->id, 'id');
905
906## Should load the third and fourth Foo objects.
907@offs = Foo->load(undef, {
908    direction => 'descend',
909    sort => 'created_on',
910    offset => 1 });
911is(@offs, 3, 'array length 3');
912isa_ok($offs[0], 'Foo');
913is($offs[0]->id, $foo4->id, 'id');
914isa_ok($offs[1], 'Foo');
915is($offs[1]->id, $binmonster->id, 'id');
916isa_ok($offs[2], 'Foo');
917is($offs[2]->id, $foo1->id, 'id');
918
919# TODO: what are these even about?
920SKIP: {
921    skip(1, '$tmp[0] undefined') unless $tmp[0];
922    ok($tmp[0] && ($tmp[0]->name eq 'foo'), 'name')
923}
924SKIP: {
925    skip(1, '$tmp[1] undefined') unless $tmp[1];
926    ok($tmp[1] && ($tmp[1]->name eq 'this'), 'name');
927}
928
9291;
Note: See TracBrowser for help on using the browser.