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

Revision 2574, 29.4 kB (checked in by mpaschal, 18 months ago)

Test avg_group_by
BugzID: 79952

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