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

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

Test conjunctions with are_objects()
BugzID: 79953

  • 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          status   => 2,     },
220        { __class  => 'Foo',
221          __wait   => 3,
222          name     => 'baz',
223          text     => 'quux',
224          status   => 1,      },
225    );
226}
227
228sub make_pc_data {
229    my $self = shift;
230    $self->make_objects(
231        { __class => 'Foo',
232          name    => 'Apple',
233          text    => 'MacBook',
234          status  => 11,        },
235        { __class => 'Foo',
236          name    => 'Linux',
237          text    => 'Ubuntu',
238          status  => 12,       },
239        { __class => 'Foo',
240          name    => 'Microsoft',
241          text    => 'Vista',
242          status  => 13,          },
243        { __class => 'Foo',
244          name    => 'Microsoft',
245          text    => 'XP',
246          status  => 10,          },
247        { __class => 'Foo',
248          name    => 'Apple',
249          text    => 'iBook',
250          status  => 10,      },
251
252        { __class => 'Bar',
253          name    => 'Silverlight',
254          status  => 2,
255          foo_id  => 3,             },
256        { __class => 'Bar',
257          name    => 'IronPython',
258          status  => 3,
259          foo_id  => 3,            },
260        { __class => 'Bar',
261          name    => 'IronRuby',
262          status  => 0,
263          foo_id  => 1,          },
264    );
265}
266
267sub basic : Tests(5) {
268    my $self = shift;
269    $self->make_basic_data();
270
271    my $foo = Foo->load(1);  # not a search
272
273    is_object(scalar Foo->load({ id => 1 }), $foo, 'Foo #1 by id hash is Foo #1');
274    is_object(scalar Foo->load({ id => 1, name => 'foo' }), $foo, 'Foo #1 by id-name hash is Foo #1');
275    is_object(scalar Foo->load({ name => 'foo' }), $foo, 'Foo #1 by name hash is Foo #1');
276    is_object(scalar Foo->load({ created_on => $foo->created_on }), $foo, 'Foo #1 by created_on hash is Foo #1');
277    is_object(scalar Foo->load({ status => 2 }), $foo, 'Foo #1 by status hash is Foo #1');
278}
279
280sub sorting : Tests(6) {
281    my $self = shift;
282    $self->make_basic_data();
283   
284    my ($tmp, @tmp);
285    my @foo = map { Foo->load($_) } (1..2);
286
287    ## Load using descending sort (newest)
288    $tmp = Foo->load(undef, {
289        sort => 'created_on',
290        direction => 'descend',
291        limit => 1 });
292    is_object($tmp, $foo[1], 'Newest Foo is Foo #2');
293
294    ## Load using ascending sort (oldest)
295    $tmp = Foo->load(undef, {
296        sort => 'created_on',
297        direction => 'ascend',
298        limit => 1 });
299    is_object($tmp, $foo[0], 'Oldest Foo is Foo #1');
300
301    ## Load using descending sort with limit = 2
302    @tmp = Foo->load(undef, {
303        sort => 'created_on',
304        direction => 'descend',
305        limit => 2 });
306    are_objects(\@tmp, [ reverse @foo ], 'Two Foos newest-first load() finds Foos #2 and #1');
307
308    ## Load using descending sort by created_on, no limit
309    @tmp = Foo->load(undef, {
310        sort => 'created_on',
311        direction => 'descend' });
312    are_objects(\@tmp, [ reverse @foo ], 'All Foos newest-first load() finds Foos #2 and #1');
313
314    ## Load using ascending sort by status, no limit
315    @tmp = Foo->load(undef, { sort => 'status', });
316    are_objects(\@tmp, [ reverse @foo ], 'All Foos lowest-status-first load() finds Foos #2 and #1');
317
318    ## Load using 'last' where status == 2
319    $tmp = Foo->load({ status => 2 }, {
320        sort => 'created_on',
321        direction => 'descend',
322        limit => 1 });
323    is_object($tmp, $foo[0], 'Newest status=2 Foo is Foo #1');
324}
325
326sub ranges : Tests(9) {
327    my $self = shift;
328    $self->make_basic_data();
329
330    my $tmp;
331    my @foo = map { Foo->load($_) } (1..2);
332   
333    ## Load using range search, one less than foo[1]->created_on and newer
334    $tmp = Foo->load(
335        { created_on => [ $foo[1]->column('created_on')-1 ] },
336        { range => { created_on => 1 } });
337    is_object($tmp, $foo[1], 'Foo from open-ended date range before Foo #2 is Foo #2');
338
339    ## Load using EXCLUSIVE range search, up through the momment $foo[1] created
340    $tmp = Foo->load(
341        { created_on => [ $foo[1]->column('created_on')-1, 
342                          $foo[1]->column('created_on') ] },
343        { range => { created_on => 1 } });
344    ok(!$tmp, "Exclusive date range load() ending at Foo #1's date found no Foos");
345
346    $tmp = Foo->load(
347        { created_on => [ $foo[1]->column('created_on'), 
348                          $foo[1]->column('created_on')+1 ] },
349        { range => { created_on => 1 } });
350    ok(!$tmp, "Exclusive date range load() starting at Foo #1's date found no Foos");
351
352    ## Load using INCLUSIVE range search, up through the momment $foo[1] created
353    $tmp = Foo->load(
354        { created_on => [ $foo[1]->column('created_on')-1, 
355                          $foo[1]->column('created_on') ] },
356        { range_incl => { created_on => 1 } });
357    ok($tmp, 'Loaded an object based on range_incl (ts-1 to ts)');
358    is_object($tmp, $foo[1], "Foo from inclusive date-range load() ending at Foo #1's date is Foo #2");
359
360    $tmp = Foo->load(
361        { created_on => [ $foo[1]->column('created_on'), 
362                          $foo[1]->column('created_on')+1 ] },
363        { range_incl => { created_on => 1 } });
364    ok($tmp, 'Loaded an object based on range_incl (ts to ts+1)');
365    is_object($tmp, $foo[1], "Foo from inclusive date-range load() starting at Foo #1's date is Foo #2");
366
367    ## Check that range searches return nothing when nothing is in the range.
368    $tmp = Foo->load( { created_on => [ undef, '19690101000000' ] },
369                  { range => { created_on => 1 } });
370    ok(!$tmp, 'Prehistoric date range load() found no Foos');
371
372    ## Range search, all items with created_on less than foo[1]->created_on
373    $tmp = Foo->load(
374        { created_on => [ undef, $foo[1]->column('created_on')-1 ] },
375        { range => { created_on => 1 } });
376    is_object($tmp, $foo[0], "Foo from exclusive open-started date-range load() ending before Foo #1 is Foo #1");
377}
378
379sub alias : Tests(2) {
380    my $self = shift;
381    $self->make_pc_data();
382
383    my $vista = Foo->load(3);  # not a search
384
385    # select * from foo, bar bar1, bar bar2
386    # where bar1.bar_foo_id = foo_id
387    # and bar2.bar_foo_id = bar1.bar_foo_id
388    # and bar1.status = 2
389    # and bar2.status = 3
390    my @a_foos = Foo->load(
391        undef,
392        { join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 2 },
393            { join => [ 'Bar', undef, { foo_id => \'= bar1.bar_foo_id', status => 3 },
394                { alias => 'bar2' } ],
395              alias => 'bar1'
396            }
397          ],
398          sort => 'created_on', direction => 'descend',
399        }
400    );
401    are_objects(\@a_foos, [ $vista ], 'Has Bars with status=2 and status=3 (alias)');
402
403    @a_foos = Foo->load(
404        undef,
405        { join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 2 },
406            { join => [ 'Bar', undef, { foo_id => \'= bar1.bar_foo_id', status => 0 },
407                { alias => 'bar2' } ],
408              alias => 'bar1'
409            }
410          ],
411          sort => 'created_on', direction => 'descend',
412        }
413    );
414    is_deeply(\@a_foos, [], 'No Foo has Bars with status=2 and status=0 (alias)');
415} 
416
417sub conjunctions : Tests(3) {
418    my $self = shift;
419    $self->make_pc_data();
420    my @foos = map { Foo->load($_) } (1..5);  # not a search
421
422    my @res = Foo->load([
423        {status => 10},
424        -or => {name => 'Apple'},
425    ]);
426    @res = sort { $a->id <=> $b->id } @res;
427    # where foo_status = 10 or foo_name = 'Apple'
428    are_objects(\@res, [ @foos[0,3,4] ], '-or results');
429
430    @res = Foo->load([
431        { status => { '<=' => 20 },
432          name => 'Apple' },
433        -and_not => { status => 11 },
434    ]);
435    @res = sort { $a->id <=> $b->id } @res;
436    # where (foo_status <= 20 and foo_name = 'Apple') and not (foo_status = 11)
437    are_objects(\@res, [ $foos[4] ], '-and_not results');
438
439    @res = Foo->load([
440        { status => 10 },
441        -or => { name => 'Apple' },
442        -or => { name => { like => '%nux' } },
443    ]);
444    @res = sort { $a->id <=> $b->id } @res;
445    # where (foo_status = 10) or (foo_name = 'Apple') or (foo_name like '%nux')
446    # (selects Apple+MacBook, Apple+iBook, Microsoft+XP, Linux+Ubuntu)
447    are_objects(\@res, [ @foos[0,1,3,4] ], 'big -or results');
448}
449
450sub clean_db : Test(teardown) {
451    MT::Test->reset_table_for(qw( Foo Bar ));
452}
453
454
455package main;
456use MT::Test;
457
458Test::Class->runtests('Test::GroupBy', 'Test::Search', +126);
459
460my($foo, @foo, @bar);
461my($tmp, @tmp);
462
463# Test for existing table
464ok(MT::Object->driver->dbd->ddl_class->column_defs('Foo'), "table mt_foo exists after upgrade");
465# Test for non-existent table
466ok(!MT::Object->driver->dbd->ddl_class->column_defs('Zot'), "table mt_zot does not exist after upgrade where undefined");
467
468## Test creating object with new
469##     test column access through column, then through AUTOLOAD
470$foo = Foo->new;
471isa_ok($foo, 'Foo', 'New Foo could be created');
472$foo->column('name', 'foo');
473is($foo->column('name'), 'foo', 'Setting name field with column() persists through access');
474$foo->name('foo');
475is($foo->name, 'foo', 'Setting name field with mutator method persists through access');
476$foo->status(2);
477$foo->text('bar');
478
479## Test saving created object
480ok($foo->save, 'A Foo could be saved');
481is($foo->id, 1, 'First Foo was given an id of 1, says accessor method');
482is($foo->column('id'), 1, 'First Foo was given an id of 1, says column()');
483
484is_object(scalar Foo->load(1), $foo, 'Foo #1 by id is Foo #1');
485
486##     Change column value, save, try to load using old value (fail?),
487##     then load again using new value
488$foo->status(0);
489ok($foo->save, 'Foo #1 saved with new status (0)');
490$tmp = Foo->load({ status => 2 });
491ok(!$tmp, 'Foo #1 no longer loads with old status (2)');
492$tmp = Foo->load({ status => 0 });
493is_object($tmp, $foo, 'Foo #1 by new status (0) is Foo #1');
494
495## Create a new object so we can do range and last/first lookups.
496## Sleep first so that they get different created_on timestamps.
497sleep(3);
498
499## Create new object for iterator testing
500$foo[0] = $foo;
501$foo[1] = Foo->new;
502$foo[1]->name('baz');
503$foo[1]->text('quux');
504$foo[1]->status(1);
505$foo[1]->save;
506
507## TEST LOADING IN VARIOUS WAYS
508
509## Load all objects via iterator
510my $iter = Foo->load_iter(undef, { sort => 'created_on', direction => 'ascend' });
511isa_ok($iter, 'CODE', "Iterator for all Foos");
512ok($tmp = $iter->(), 'Iterator for our two Foos had one object');
513is_object($tmp, $foo[0], "All Foo iterator's first Foo is Foo #1");
514ok($tmp = $iter->(), 'Iterator for our two Foos had two objects');
515is_object($tmp, $foo[1], "All Foo iterator's second Foo is Foo #2");
516ok(!$iter->(), 'Iterator for our two Foos did not have a third object');
517
518## Load all objects with status == 1 via iterator
519$iter = Foo->load_iter({ status => 1 });
520isa_ok($iter, 'CODE', "Iterator for status=1 Foos");
521ok($tmp = $iter->(), 'Iterator for our status=1 Foos had one object');
522is_object($tmp, $foo[1], "Status=1 Foo iterator's first Foo is Foo #2");
523ok(!$iter->(), "Iterator for our status=1 Foos did not have a second object");
524
525## Load using non-existent ID (should fail)
526$tmp = Foo->load(3);
527ok(!$tmp, 'There is no Foo #3');
528
529
530## Get count of objects
531is(Foo->count(), 2, 'Count of all Foos finds both');
532is(Foo->count({ status => 0 }), 1, 'Count of all status=0 Foos finds all one');
533my $ranged_count = Foo->count(
534    { created_on => [ $foo[1]->column('created_on')-1 ] },
535    { range => { created_on => 1 } }
536);
537is($ranged_count, 1, 'Count of all Foos in open-ended date range starting before Foo #1 finds all one');
538
539## Update status for later tests.
540$foo[0]->status(2);
541$foo[0]->save;
542
543## Test start_val loads.
544## Given the first Foo object, should load the "next" one
545## (the one with a larger created_on time)
546$tmp = Foo->load(undef, {
547    limit => 1,
548    sort => 'created_on',
549    direction => 'ascend',
550    start_val => $foo[0]->created_on });
551is_object($tmp, $foo[1], 'Next newer Foo after Foo #1 is Foo #2');
552
553## Given the first Foo object, try to load the "previous" one
554## (the one with a smaller created_on time). This should fail.
555$tmp = Foo->load(undef, {
556    limit => 1,
557    sort => 'created_on',
558    direction => 'descend',
559    start_val => $foo[0]->created_on });
560ok(!$tmp, 'Search for next older Foo before Foo #1 found none');
561
562## Given the second Foo object, try to load the "previous" one
563## (the one with a smaller created_on time). This should work.
564$tmp = Foo->load(undef, {
565    limit => 1,
566    sort => 'created_on',
567    direction => 'descend',
568    start_val => $foo[1]->created_on });
569is_object($tmp, $foo[0], 'Next older Foo before Foo #2 is Foo #1');
570
571## Given the second Foo object, try to load the "next" one
572## (the one with a larger created_on time). This should fail.
573$tmp = Foo->load(undef, {
574    limit => 1,
575    sort => 'created_on',
576    direction => 'ascend',
577    start_val => $foo[1]->created_on });
578ok(!$tmp, 'Search for next newer Foo after Foo #2 found none');
579
580## Now, given the second Foo object's created_on - 1, try to
581## load the "previous" one. This should work.
582$tmp = Foo->load(undef, {
583    limit => 1,
584    sort => 'created_on',
585    direction => 'descend',
586    start_val => $foo[1]->created_on-1 });
587is_object($tmp, $foo[0], 'Next older Foo before just before Foo #2 is Foo #1');
588
589## Now, given the second Foo object's created_on - 1, try to
590## load the "next" one. This should work.
591$tmp = Foo->load(undef, {
592    limit => 1,
593    sort => 'created_on',
594    direction => 'ascend',
595    start_val => $foo[1]->created_on-1 });
596is_object($tmp, $foo[1], 'Next newer Foo after just before Foo #2 is Foo #2');
597
598## Override created_on timestamp, make sure it works
599my $ts = substr($foo[1]->created_on, 0, -4) . '0000';
600$foo[1]->created_on($ts);
601$foo[1]->save;
602
603@tmp = Foo->load(undef, {
604    sort => 'created_on',
605    direction => 'descend',
606    limit => 2 });
607are_objects(\@tmp, \@foo, 'Time-traveled Foos newest-first are Foos #1 and #2');
608
609## Test limit of 2 with direction descend, but without
610## a sort option. This should sort by the most recently-added
611## records, ie. sorted by ID, basically.
612@tmp = Foo->load(undef, {
613    direction => 'descend',
614    limit => 2 });
615are_objects(\@tmp, [ reverse @foo ], 'Foos highest-id-first are Foos #2 and #1');
616
617## Test loading using offset.
618## Should load the second Foo object.
619$tmp = Foo->load(undef, {
620    direction => 'descend',
621    sort => 'created_on',
622    limit => 1,
623    offset => 1 });
624is_object($tmp, $foo[1], 'Second newest Foo is Foo #2');
625
626## We only have 2 Foo objects, so this should load
627## only the second Foo object (because offset is 1).
628@tmp = Foo->load(undef, {
629    direction => 'descend',
630    sort => 'created_on',
631    limit => 2,
632    offset => 1 });
633are_objects(\@tmp, [ $foo[1] ], 'Second and third newest Foos is just Foo #2');
634
635## Should load the first Foo object (ascend with offset of 1).
636$tmp = Foo->load(undef, {
637    direction => 'ascend',
638    sort => 'created_on',
639    limit => 1,
640    offset => 1 });
641is_object($tmp, $foo[0], 'Second oldest Foo is Foo #1');
642
643## Now test join loads.
644## First we need to create a couple of Bar objects.
645$bar[0] = Bar->new;
646$bar[0]->foo_id($foo[1]->id);
647$bar[0]->name('bar0');
648$bar[0]->status(0);
649ok($bar[0]->save, 'saved');
650sleep(2);  ## Sleep to ensure created_on timestamps are unique
651
652$bar[1] = Bar->new;
653$bar[1]->foo_id($foo[1]->id);
654$bar[1]->name('bar1');
655$bar[1]->status(1);
656ok($bar[1]->save, 'saved');
657sleep(2);  ## Sleep to ensure created_on timestamps are unique
658
659$bar[2] = Bar->new;
660$bar[2]->foo_id($foo[0]->id);
661$bar[2]->name('bar2');
662$bar[2]->status(0);
663ok($bar[2]->save, 'saved');
664sleep(2);  ## Sleep to ensure created_on timestamps are unique
665
666## Get a count of all Foo objects in order of most recently
667## created Bar object. No uniqueness requirement. This tests
668## the on_load_complete temporary table stuff with count.
669
670is(Foo->count(undef,
671    { join => [ 'Bar', 'foo_id',
672                undef,
673                { unique => 1,
674                  sort => 'created_on',
675                  direction => 'descend', } ] }), 2, 'There are 2 unique Foos associated with Bars');
676
677## Now load all Foo objects in order of most recently
678## created Bar object. Make sure they are unique.
679@tmp = Foo->load(undef,
680    { join => [ 'Bar', 'foo_id',
681                undef,
682                { sort => 'created_on',
683                  direction => 'descend',
684                  unique => 1 } ] });
685are_objects(\@tmp, \@foo, 'unique Foos associated with Bars, oldest first');
686
687## Load all Foo objects in order of most recently
688## created Bar object. No uniqueness requirement.
689@tmp = Foo->load(undef,
690    { join => [ 'Bar', 'foo_id',
691                undef,
692                { sort => 'created_on',
693                  direction => 'descend', } ] });
694are_objects(\@tmp, [ @foo, $foo[1] ], 'Foos associated with Bars, oldest first');
695
696## Load last 1 Foo object in order of most recently
697## created Bar object.
698@tmp = Foo->load(undef,
699    { join => [ 'Bar', 'foo_id',
700                undef,
701                { sort => 'created_on',
702                  direction => 'descend',
703                  unique => 1,
704                  limit => 1, } ] });
705are_objects(\@tmp, [ $foo[0] ], 'Foos associated with oldest Bar');
706
707## Load all Foo objects where Bar.name = 'bar0'
708@tmp = Foo->load(undef,
709    { join => [ 'Bar', 'foo_id',
710                { name => 'bar0' },
711                { sort => 'created_on',
712                  direction => 'descend',
713                  unique => 1, } ] });
714are_objects(\@tmp, [ $foo[1] ], 'Foos associated with Bars named bar0');
715
716## foo[1] is older than foo[0] because we overrode the timestamp,
717## so this should load foo[0]
718@tmp = Foo->load(undef,
719    { sort => 'created_on', direction => 'descend', limit => 1,
720    join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
721are_objects(\@tmp, [ $foo[0] ], 'One Foo associated with Bars of status=0');
722
723## This is the same join as the last one, but without the limit--so
724## we should get both Foo objects this time, in descending order.
725@tmp = Foo->load(undef,
726    { sort => 'created_on', direction => 'descend',
727      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
728are_objects(\@tmp, \@foo, 'All Foos associated with Bars of status=0');
729
730## Filter join results by providing a value for 'status'; only Foo[0]
731## has a 'status' == 2, so only that record should be returned.
732@tmp = Foo->load({ status => 2 },
733    { sort => 'created_on', direction => 'descend',
734      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
735are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 associated with Bars of status=0');
736
737# Join across a column.
738@tmp = Foo->load({},
739    { sort => 'created_on', direction => 'descend',
740      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
741are_objects(\@tmp, \@foo, 'Foos loaded by explicit join across columns');
742
743@tmp = Foo->load({ status => 2 },
744    { sort => 'created_on', direction => 'descend',
745      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
746are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 loaded by explicit join across columns');
747
748## TEST EXISTS METHOD
749ok($foo->exists, 'First Foo long saved exists in db');
750$tmp = Foo->new;
751ok(!$tmp->exists, 'New Foo just created does not exist in db');
752$tmp->id(5);
753ok(!$tmp->exists, 'New Foo just created with fake id does not exist in db');
754
755## Change foo[1]->status so that its value is unique (for index)
756$foo[1]->status(5);
757ok($foo[1]->save, 'saved');
758ok(Foo->load({ status => 5 }), 'loaded' );
759
760## Test remove
761ok($foo[1]->remove, 'removed');
762ok(! Foo->load(2), 'not loaded');
763ok(! Foo->load({ status => 5 }), 'not loaded');
764ok(! Foo->load({ name => $foo[1]->name }), 'not loaded');
765ok(! Foo->load({ created_on => $foo[1]->created_on }), 'not loaded');
766
767## Test methods:
768##     * properties
769my $props1 = Foo->properties;
770is($props1->{audit}, 1, 'audit');
771is(scalar keys %{ $props1->{indexes} }, 3, 'indexes');
772is($props1->{primary_key}, 'id', 'id');
773is(scalar @{ $props1->{columns} }, 9, 'columns');
774my $props2 = $foo->properties;
775is($props1, $props2, "$props1 is $props2");  ## Same address, because same hashref
776
777##     * column_names
778my $cols = $foo->column_names;
779isa_ok($cols, 'ARRAY');
780my %cols = map { $_ => 1 } @$cols;
781for (qw(id name status text data created_on created_by modified_on modified_by)) {
782    ok($cols{$_}, 'cols');
783}
784
785##     * column_values
786my $vals = $foo->column_values;
787isa_ok($vals, 'HASH');
788is($vals->{id}, $foo->id, 'id');
789is($vals->{name}, $foo->name, 'name');
790is($vals->{status}, $foo->status, 'status');
791is($vals->{text}, $foo->text, 'text');
792is($vals->{created_on}, $foo->created_on, 'created_on');
793is($vals->{created_by}, $foo->created_by, 'created_by');
794is($vals->{modified_on}, $foo->modified_on, 'modified_on');
795is($vals->{modified_by}, $foo->modified_by, 'modified_by');
796
797##     * set_values
798$vals = {
799    id => 5,
800    name => 'baz',
801    status => 7,
802    text => 'quux',
803    created_on => 13209,
804    created_by => 'bar',
805    #modified_on => 39023, modified_by auto-set modified_on in our new code.
806    modified_by => 'foo',
807};
808$foo->set_values($vals);
809for my $col (keys %$vals) {
810    is($vals->{$col}, $foo->column($col), $col);
811}
812
813##     * binary data
814
815my $binmonster = Foo->new;
816
817$vals = {
818    funky => "yes",
819    monkey => "no",
820};
821
822require MT::Serialize;
823my $srlzr = MT::Serialize->new('MT');
824$binmonster->data($srlzr->serialize(\$vals));
825my $x = $binmonster->save();
826warn 'Failed binary data test: ' . $binmonster->errstr() unless $x;
827ok($x, 'saved');
828ok($binmonster->id, 'id');
829Foo->driver->clear_cache if Foo->driver->can('clear_cache');
830my $chk = Foo->load($binmonster->id);
831if ($chk) {
832    my $chk_data = $chk->data;
833    my $chk_vals = $srlzr->unserialize($chk_data);
834    foreach (keys %$vals) {
835        is($$chk_vals->{$_}, $vals->{$_}, $_);
836    }
837} else {
838    foreach (keys %$vals) {
839        ok(0, $_);
840    }
841}
842
843##     * datasource
844is($foo->table_name, 'mt_' . $foo->datasource, 'datasource');
845
846##     * clone
847my $clone = $foo->clone_all;
848for my $col (@$cols) {
849    is($clone->column($col), $foo->column($col), $col);
850}
851
852## Sleep first so that they get different created_on timestamps.
853sleep(3);
854
855Foo->set_by_key({name => "this"});
856my $obj = Foo->load({name => "this"});
857isa_ok($obj, 'Foo');
858
859Foo->set_by_key({name => "this"}, {status => 42});
860$obj = Foo->load({name => "this"});
861is($obj && $obj->status, 42, 'status');
862
863Foo->set_by_key({name => "this"}, {status => 47});
864$obj = Foo->load({name => "this"});
865is($obj && $obj->status, 47, 'status');
866
867Foo->set_by_key({name => "this", status => 47}, {text => "spiffy"});
868$obj = Foo->load({name => "this", status => 47});
869is($obj && $obj->text, 'spiffy', 'text');
870
871sleep(3);
872
873Foo->set_by_key({name => "that"}, {text => "Once"});
874$obj = Foo->load({name => "that"});
875is($obj && $obj->text, 'Once', 'text');
876
877Foo->driver->clear_cache if Foo->driver->can('clear_cache');
878## Load use direct set of values for non-PK column
879@tmp = Foo->load({ name => [qw(foo this)] });
880@tmp = sort {$a->name cmp $b->name} @tmp;
881is(@tmp, 2, 'array length 2');
882
883is(Foo->count(), 4, 'check number of Foos');
884
885## check offsets without limits
886## Should load the third and fourth Foo objects.
887my $foo4 = Foo->load({name => "this"});
888my $foo5 = Foo->load({name => "that"});
889my $foo1 = Foo->load(undef, { 'sort' => 'created_on', 'direction' => 'ascend' });
890my @offs = Foo->load(undef, {
891    direction => 'ascend',
892    sort => 'created_on',
893    offset => 2 });
894is(@offs, 2, 'array length 2');
895isa_ok($offs[0], 'Foo');
896is($offs[0]->id, $foo4->id, 'id');
897isa_ok($offs[1], 'Foo');
898is($offs[1]->id, $foo5->id, 'id');
899
900## Should load the third and fourth Foo objects.
901@offs = Foo->load(undef, {
902    direction => 'descend',
903    sort => 'created_on',
904    offset => 1 });
905is(@offs, 3, 'array length 3');
906isa_ok($offs[0], 'Foo');
907is($offs[0]->id, $foo4->id, 'id');
908isa_ok($offs[1], 'Foo');
909is($offs[1]->id, $binmonster->id, 'id');
910isa_ok($offs[2], 'Foo');
911is($offs[2]->id, $foo1->id, 'id');
912
913# TODO: what are these even about?
914SKIP: {
915    skip(1, '$tmp[0] undefined') unless $tmp[0];
916    ok($tmp[0] && ($tmp[0]->name eq 'foo'), 'name')
917}
918SKIP: {
919    skip(1, '$tmp[1] undefined') unless $tmp[1];
920    ok($tmp[1] && ($tmp[1]->name eq 'this'), 'name');
921}
922
9231;
Note: See TracBrowser for help on using the browser.