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

Revision 2590, 29.2 kB (checked in by mpaschal, 18 months ago)

Move tests for alias and conjunctor support into Test::Search
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
421    my $count = Foo->count( [{status => 10}, -or => {name => 'Apple'}] );
422    # ==> select count(*) from mt_foo where foo_status = 10 or foo_name = 'Apple'
423    is($count, 3, '-or count');
424
425    $count = Foo->count( [ { status => { '<=' => 20 }, name => 'Apple' }, -and_not => { status => 11 } ] );
426    # ==> select count(*) from mt_foo where (foo_status <= 20 and foo_name = 'Apple') and not (foo_status = 11)
427    is($count, 1, '-and_not count');
428
429    $count = Foo->count( [
430        { status => 10 },
431        -or => { name => 'Apple' },
432        -or => { name => { like => '%nux' } },
433    ] );
434    # ==> select count(*) from mt_foo where (foo_status = 10) or (foo_name = 'Apple') or (foo_name like '%nux')
435    # (selects Apple+MacBook, Apple+iBook, Microsoft+XP, Linux+Ubuntu)
436    is($count, 4, '-or count, 3 clauses');
437}
438
439sub clean_db : Test(teardown) {
440    MT::Test->reset_table_for(qw( Foo Bar ));
441}
442
443
444package main;
445use MT::Test;
446
447Test::Class->runtests('Test::GroupBy', 'Test::Search', +126);
448
449my($foo, @foo, @bar);
450my($tmp, @tmp);
451
452# Test for existing table
453ok(MT::Object->driver->dbd->ddl_class->column_defs('Foo'), "table mt_foo exists after upgrade");
454# Test for non-existent table
455ok(!MT::Object->driver->dbd->ddl_class->column_defs('Zot'), "table mt_zot does not exist after upgrade where undefined");
456
457## Test creating object with new
458##     test column access through column, then through AUTOLOAD
459$foo = Foo->new;
460isa_ok($foo, 'Foo', 'New Foo could be created');
461$foo->column('name', 'foo');
462is($foo->column('name'), 'foo', 'Setting name field with column() persists through access');
463$foo->name('foo');
464is($foo->name, 'foo', 'Setting name field with mutator method persists through access');
465$foo->status(2);
466$foo->text('bar');
467
468## Test saving created object
469ok($foo->save, 'A Foo could be saved');
470is($foo->id, 1, 'First Foo was given an id of 1, says accessor method');
471is($foo->column('id'), 1, 'First Foo was given an id of 1, says column()');
472
473is_object(scalar Foo->load(1), $foo, 'Foo #1 by id is Foo #1');
474
475##     Change column value, save, try to load using old value (fail?),
476##     then load again using new value
477$foo->status(0);
478ok($foo->save, 'Foo #1 saved with new status (0)');
479$tmp = Foo->load({ status => 2 });
480ok(!$tmp, 'Foo #1 no longer loads with old status (2)');
481$tmp = Foo->load({ status => 0 });
482is_object($tmp, $foo, 'Foo #1 by new status (0) is Foo #1');
483
484## Create a new object so we can do range and last/first lookups.
485## Sleep first so that they get different created_on timestamps.
486sleep(3);
487
488## Create new object for iterator testing
489$foo[0] = $foo;
490$foo[1] = Foo->new;
491$foo[1]->name('baz');
492$foo[1]->text('quux');
493$foo[1]->status(1);
494$foo[1]->save;
495
496## TEST LOADING IN VARIOUS WAYS
497
498## Load all objects via iterator
499my $iter = Foo->load_iter(undef, { sort => 'created_on', direction => 'ascend' });
500isa_ok($iter, 'CODE', "Iterator for all Foos");
501ok($tmp = $iter->(), 'Iterator for our two Foos had one object');
502is_object($tmp, $foo[0], "All Foo iterator's first Foo is Foo #1");
503ok($tmp = $iter->(), 'Iterator for our two Foos had two objects');
504is_object($tmp, $foo[1], "All Foo iterator's second Foo is Foo #2");
505ok(!$iter->(), 'Iterator for our two Foos did not have a third object');
506
507## Load all objects with status == 1 via iterator
508$iter = Foo->load_iter({ status => 1 });
509isa_ok($iter, 'CODE', "Iterator for status=1 Foos");
510ok($tmp = $iter->(), 'Iterator for our status=1 Foos had one object');
511is_object($tmp, $foo[1], "Status=1 Foo iterator's first Foo is Foo #2");
512ok(!$iter->(), "Iterator for our status=1 Foos did not have a second object");
513
514## Load using non-existent ID (should fail)
515$tmp = Foo->load(3);
516ok(!$tmp, 'There is no Foo #3');
517
518
519## Get count of objects
520is(Foo->count(), 2, 'Count of all Foos finds both');
521is(Foo->count({ status => 0 }), 1, 'Count of all status=0 Foos finds all one');
522my $ranged_count = Foo->count(
523    { created_on => [ $foo[1]->column('created_on')-1 ] },
524    { range => { created_on => 1 } }
525);
526is($ranged_count, 1, 'Count of all Foos in open-ended date range starting before Foo #1 finds all one');
527
528## Update status for later tests.
529$foo[0]->status(2);
530$foo[0]->save;
531
532## Test start_val loads.
533## Given the first Foo object, should load the "next" one
534## (the one with a larger created_on time)
535$tmp = Foo->load(undef, {
536    limit => 1,
537    sort => 'created_on',
538    direction => 'ascend',
539    start_val => $foo[0]->created_on });
540is_object($tmp, $foo[1], 'Next newer Foo after Foo #1 is Foo #2');
541
542## Given the first Foo object, try to load the "previous" one
543## (the one with a smaller created_on time). This should fail.
544$tmp = Foo->load(undef, {
545    limit => 1,
546    sort => 'created_on',
547    direction => 'descend',
548    start_val => $foo[0]->created_on });
549ok(!$tmp, 'Search for next older Foo before Foo #1 found none');
550
551## Given the second Foo object, try to load the "previous" one
552## (the one with a smaller created_on time). This should work.
553$tmp = Foo->load(undef, {
554    limit => 1,
555    sort => 'created_on',
556    direction => 'descend',
557    start_val => $foo[1]->created_on });
558is_object($tmp, $foo[0], 'Next older Foo before Foo #2 is Foo #1');
559
560## Given the second Foo object, try to load the "next" one
561## (the one with a larger created_on time). This should fail.
562$tmp = Foo->load(undef, {
563    limit => 1,
564    sort => 'created_on',
565    direction => 'ascend',
566    start_val => $foo[1]->created_on });
567ok(!$tmp, 'Search for next newer Foo after Foo #2 found none');
568
569## Now, given the second Foo object's created_on - 1, try to
570## load the "previous" one. This should work.
571$tmp = Foo->load(undef, {
572    limit => 1,
573    sort => 'created_on',
574    direction => 'descend',
575    start_val => $foo[1]->created_on-1 });
576is_object($tmp, $foo[0], 'Next older Foo before just before Foo #2 is Foo #1');
577
578## Now, given the second Foo object's created_on - 1, try to
579## load the "next" one. This should work.
580$tmp = Foo->load(undef, {
581    limit => 1,
582    sort => 'created_on',
583    direction => 'ascend',
584    start_val => $foo[1]->created_on-1 });
585is_object($tmp, $foo[1], 'Next newer Foo after just before Foo #2 is Foo #2');
586
587## Override created_on timestamp, make sure it works
588my $ts = substr($foo[1]->created_on, 0, -4) . '0000';
589$foo[1]->created_on($ts);
590$foo[1]->save;
591
592@tmp = Foo->load(undef, {
593    sort => 'created_on',
594    direction => 'descend',
595    limit => 2 });
596are_objects(\@tmp, \@foo, 'Time-traveled Foos newest-first are Foos #1 and #2');
597
598## Test limit of 2 with direction descend, but without
599## a sort option. This should sort by the most recently-added
600## records, ie. sorted by ID, basically.
601@tmp = Foo->load(undef, {
602    direction => 'descend',
603    limit => 2 });
604are_objects(\@tmp, [ reverse @foo ], 'Foos highest-id-first are Foos #2 and #1');
605
606## Test loading using offset.
607## Should load the second Foo object.
608$tmp = Foo->load(undef, {
609    direction => 'descend',
610    sort => 'created_on',
611    limit => 1,
612    offset => 1 });
613is_object($tmp, $foo[1], 'Second newest Foo is Foo #2');
614
615## We only have 2 Foo objects, so this should load
616## only the second Foo object (because offset is 1).
617@tmp = Foo->load(undef, {
618    direction => 'descend',
619    sort => 'created_on',
620    limit => 2,
621    offset => 1 });
622are_objects(\@tmp, [ $foo[1] ], 'Second and third newest Foos is just Foo #2');
623
624## Should load the first Foo object (ascend with offset of 1).
625$tmp = Foo->load(undef, {
626    direction => 'ascend',
627    sort => 'created_on',
628    limit => 1,
629    offset => 1 });
630is_object($tmp, $foo[0], 'Second oldest Foo is Foo #1');
631
632## Now test join loads.
633## First we need to create a couple of Bar objects.
634$bar[0] = Bar->new;
635$bar[0]->foo_id($foo[1]->id);
636$bar[0]->name('bar0');
637$bar[0]->status(0);
638ok($bar[0]->save, 'saved');
639sleep(2);  ## Sleep to ensure created_on timestamps are unique
640
641$bar[1] = Bar->new;
642$bar[1]->foo_id($foo[1]->id);
643$bar[1]->name('bar1');
644$bar[1]->status(1);
645ok($bar[1]->save, 'saved');
646sleep(2);  ## Sleep to ensure created_on timestamps are unique
647
648$bar[2] = Bar->new;
649$bar[2]->foo_id($foo[0]->id);
650$bar[2]->name('bar2');
651$bar[2]->status(0);
652ok($bar[2]->save, 'saved');
653sleep(2);  ## Sleep to ensure created_on timestamps are unique
654
655## Get a count of all Foo objects in order of most recently
656## created Bar object. No uniqueness requirement. This tests
657## the on_load_complete temporary table stuff with count.
658
659is(Foo->count(undef,
660    { join => [ 'Bar', 'foo_id',
661                undef,
662                { unique => 1,
663                  sort => 'created_on',
664                  direction => 'descend', } ] }), 2, 'There are 2 unique Foos associated with Bars');
665
666## Now load all Foo objects in order of most recently
667## created Bar object. Make sure they are unique.
668@tmp = Foo->load(undef,
669    { join => [ 'Bar', 'foo_id',
670                undef,
671                { sort => 'created_on',
672                  direction => 'descend',
673                  unique => 1 } ] });
674are_objects(\@tmp, \@foo, 'unique Foos associated with Bars, oldest first');
675
676## Load all Foo objects in order of most recently
677## created Bar object. No uniqueness requirement.
678@tmp = Foo->load(undef,
679    { join => [ 'Bar', 'foo_id',
680                undef,
681                { sort => 'created_on',
682                  direction => 'descend', } ] });
683are_objects(\@tmp, [ @foo, $foo[1] ], 'Foos associated with Bars, oldest first');
684
685## Load last 1 Foo object in order of most recently
686## created Bar object.
687@tmp = Foo->load(undef,
688    { join => [ 'Bar', 'foo_id',
689                undef,
690                { sort => 'created_on',
691                  direction => 'descend',
692                  unique => 1,
693                  limit => 1, } ] });
694are_objects(\@tmp, [ $foo[0] ], 'Foos associated with oldest Bar');
695
696## Load all Foo objects where Bar.name = 'bar0'
697@tmp = Foo->load(undef,
698    { join => [ 'Bar', 'foo_id',
699                { name => 'bar0' },
700                { sort => 'created_on',
701                  direction => 'descend',
702                  unique => 1, } ] });
703are_objects(\@tmp, [ $foo[1] ], 'Foos associated with Bars named bar0');
704
705## foo[1] is older than foo[0] because we overrode the timestamp,
706## so this should load foo[0]
707@tmp = Foo->load(undef,
708    { sort => 'created_on', direction => 'descend', limit => 1,
709    join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
710are_objects(\@tmp, [ $foo[0] ], 'One Foo associated with Bars of status=0');
711
712## This is the same join as the last one, but without the limit--so
713## we should get both Foo objects this time, in descending order.
714@tmp = Foo->load(undef,
715    { sort => 'created_on', direction => 'descend',
716      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
717are_objects(\@tmp, \@foo, 'All Foos associated with Bars of status=0');
718
719## Filter join results by providing a value for 'status'; only Foo[0]
720## has a 'status' == 2, so only that record should be returned.
721@tmp = Foo->load({ status => 2 },
722    { sort => 'created_on', direction => 'descend',
723      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
724are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 associated with Bars of status=0');
725
726# Join across a column.
727@tmp = Foo->load({},
728    { sort => 'created_on', direction => 'descend',
729      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
730are_objects(\@tmp, \@foo, 'Foos loaded by explicit join across columns');
731
732@tmp = Foo->load({ status => 2 },
733    { sort => 'created_on', direction => 'descend',
734      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
735are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 loaded by explicit join across columns');
736
737## TEST EXISTS METHOD
738ok($foo->exists, 'First Foo long saved exists in db');
739$tmp = Foo->new;
740ok(!$tmp->exists, 'New Foo just created does not exist in db');
741$tmp->id(5);
742ok(!$tmp->exists, 'New Foo just created with fake id does not exist in db');
743
744## Change foo[1]->status so that its value is unique (for index)
745$foo[1]->status(5);
746ok($foo[1]->save, 'saved');
747ok(Foo->load({ status => 5 }), 'loaded' );
748
749## Test remove
750ok($foo[1]->remove, 'removed');
751ok(! Foo->load(2), 'not loaded');
752ok(! Foo->load({ status => 5 }), 'not loaded');
753ok(! Foo->load({ name => $foo[1]->name }), 'not loaded');
754ok(! Foo->load({ created_on => $foo[1]->created_on }), 'not loaded');
755
756## Test methods:
757##     * properties
758my $props1 = Foo->properties;
759is($props1->{audit}, 1, 'audit');
760is(scalar keys %{ $props1->{indexes} }, 3, 'indexes');
761is($props1->{primary_key}, 'id', 'id');
762is(scalar @{ $props1->{columns} }, 9, 'columns');
763my $props2 = $foo->properties;
764is($props1, $props2, "$props1 is $props2");  ## Same address, because same hashref
765
766##     * column_names
767my $cols = $foo->column_names;
768isa_ok($cols, 'ARRAY');
769my %cols = map { $_ => 1 } @$cols;
770for (qw(id name status text data created_on created_by modified_on modified_by)) {
771    ok($cols{$_}, 'cols');
772}
773
774##     * column_values
775my $vals = $foo->column_values;
776isa_ok($vals, 'HASH');
777is($vals->{id}, $foo->id, 'id');
778is($vals->{name}, $foo->name, 'name');
779is($vals->{status}, $foo->status, 'status');
780is($vals->{text}, $foo->text, 'text');
781is($vals->{created_on}, $foo->created_on, 'created_on');
782is($vals->{created_by}, $foo->created_by, 'created_by');
783is($vals->{modified_on}, $foo->modified_on, 'modified_on');
784is($vals->{modified_by}, $foo->modified_by, 'modified_by');
785
786##     * set_values
787$vals = {
788    id => 5,
789    name => 'baz',
790    status => 7,
791    text => 'quux',
792    created_on => 13209,
793    created_by => 'bar',
794    #modified_on => 39023, modified_by auto-set modified_on in our new code.
795    modified_by => 'foo',
796};
797$foo->set_values($vals);
798for my $col (keys %$vals) {
799    is($vals->{$col}, $foo->column($col), $col);
800}
801
802##     * binary data
803
804my $binmonster = Foo->new;
805
806$vals = {
807    funky => "yes",
808    monkey => "no",
809};
810
811require MT::Serialize;
812my $srlzr = MT::Serialize->new('MT');
813$binmonster->data($srlzr->serialize(\$vals));
814my $x = $binmonster->save();
815warn 'Failed binary data test: ' . $binmonster->errstr() unless $x;
816ok($x, 'saved');
817ok($binmonster->id, 'id');
818Foo->driver->clear_cache if Foo->driver->can('clear_cache');
819my $chk = Foo->load($binmonster->id);
820if ($chk) {
821    my $chk_data = $chk->data;
822    my $chk_vals = $srlzr->unserialize($chk_data);
823    foreach (keys %$vals) {
824        is($$chk_vals->{$_}, $vals->{$_}, $_);
825    }
826} else {
827    foreach (keys %$vals) {
828        ok(0, $_);
829    }
830}
831
832##     * datasource
833is($foo->table_name, 'mt_' . $foo->datasource, 'datasource');
834
835##     * clone
836my $clone = $foo->clone_all;
837for my $col (@$cols) {
838    is($clone->column($col), $foo->column($col), $col);
839}
840
841## Sleep first so that they get different created_on timestamps.
842sleep(3);
843
844Foo->set_by_key({name => "this"});
845my $obj = Foo->load({name => "this"});
846isa_ok($obj, 'Foo');
847
848Foo->set_by_key({name => "this"}, {status => 42});
849$obj = Foo->load({name => "this"});
850is($obj && $obj->status, 42, 'status');
851
852Foo->set_by_key({name => "this"}, {status => 47});
853$obj = Foo->load({name => "this"});
854is($obj && $obj->status, 47, 'status');
855
856Foo->set_by_key({name => "this", status => 47}, {text => "spiffy"});
857$obj = Foo->load({name => "this", status => 47});
858is($obj && $obj->text, 'spiffy', 'text');
859
860sleep(3);
861
862Foo->set_by_key({name => "that"}, {text => "Once"});
863$obj = Foo->load({name => "that"});
864is($obj && $obj->text, 'Once', 'text');
865
866Foo->driver->clear_cache if Foo->driver->can('clear_cache');
867## Load use direct set of values for non-PK column
868@tmp = Foo->load({ name => [qw(foo this)] });
869@tmp = sort {$a->name cmp $b->name} @tmp;
870is(@tmp, 2, 'array length 2');
871
872is(Foo->count(), 4, 'check number of Foos');
873
874## check offsets without limits
875## Should load the third and fourth Foo objects.
876my $foo4 = Foo->load({name => "this"});
877my $foo5 = Foo->load({name => "that"});
878my $foo1 = Foo->load(undef, { 'sort' => 'created_on', 'direction' => 'ascend' });
879my @offs = Foo->load(undef, {
880    direction => 'ascend',
881    sort => 'created_on',
882    offset => 2 });
883is(@offs, 2, 'array length 2');
884isa_ok($offs[0], 'Foo');
885is($offs[0]->id, $foo4->id, 'id');
886isa_ok($offs[1], 'Foo');
887is($offs[1]->id, $foo5->id, 'id');
888
889## Should load the third and fourth Foo objects.
890@offs = Foo->load(undef, {
891    direction => 'descend',
892    sort => 'created_on',
893    offset => 1 });
894is(@offs, 3, 'array length 3');
895isa_ok($offs[0], 'Foo');
896is($offs[0]->id, $foo4->id, 'id');
897isa_ok($offs[1], 'Foo');
898is($offs[1]->id, $binmonster->id, 'id');
899isa_ok($offs[2], 'Foo');
900is($offs[2]->id, $foo1->id, 'id');
901
902# TODO: what are these even about?
903SKIP: {
904    skip(1, '$tmp[0] undefined') unless $tmp[0];
905    ok($tmp[0] && ($tmp[0]->name eq 'foo'), 'name')
906}
907SKIP: {
908    skip(1, '$tmp[1] undefined') unless $tmp[1];
909    ok($tmp[1] && ($tmp[1]->name eq 'this'), 'name');
910}
911
9121;
Note: See TracBrowser for help on using the browser.