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

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

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