DBIx-QuickORM
view release on metacpan or search on metacpan
t/AI/desync_guard.t view on Meta::CPAN
use Test2::V0;
use DBI;
use File::Temp qw/tempdir/;
# Exercises desync detection and the save guard in DBIx::QuickORM::Row:
# a refresh that changes stored values underneath pending changes flags the
# affected fields, save() croaks while any flag remains (track_desync is on
# by default), and discard / force_sync / per-field update resolve it.
BEGIN {
skip_all "DBD::SQLite is required for these tests"
unless eval { require DBD::SQLite; 1 };
}
require DBIx::QuickORM;
my $dir = tempdir(CLEANUP => 1);
my $file = "$dir/desync.sqlite";
my $dsn = "dbi:SQLite:dbname=$file";
{
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
$dbh->do('CREATE TABLE people (person_id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT, age INTEGER)');
$dbh->disconnect;
}
sub db_change {
my ($pk, %set) = @_;
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
$dbh->do("UPDATE people SET $_ = ? WHERE person_id = ?", undef, $set{$_}, $pk) for keys %set;
$dbh->disconnect;
}
sub db_value {
my ($col, $pk) = @_;
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
my ($val) = $dbh->selectrow_array("SELECT $col FROM people WHERE person_id = ?", undef, $pk);
$dbh->disconnect;
return $val;
}
my $con = DBIx::QuickORM->quick(credentials => {dsn => $dsn});
my $h = $con->handle('people');
sub desynced_row {
my (%fields) = @_;
my $row = $h->insert({name => 'sync', email => 'sync@example.com', age => 1, %fields});
my $pk = $row->field('person_id');
$row->field(name => 'mine');
db_change($pk, name => 'theirs');
$row->refresh;
return ($row, $pk);
}
subtest refresh_with_pending_flags_desync => sub {
my ($row, $pk) = desynced_row();
ok($row->is_desynced, "row is flagged as desynced");
ok($row->field_is_desynced('name'), "the conflicted field is flagged");
ok(!$row->field_is_desynced('email'), "untouched fields are not flagged");
is($row->stored_field('name'), 'theirs', "stored view has the refreshed value");
is($row->pending_field('name'), 'mine', "pending view kept the local edit");
like(dies { $row->save }, qr/This row is out of sync/, "save() croaks on a desynced row");
is(db_value(name => $pk), 'theirs', "database value untouched by the failed save");
};
subtest discard_resolves_desync => sub {
my ($row, $pk) = desynced_row();
$row->discard;
ok(!$row->is_desynced, "discard cleared the desync flags");
ok(!$row->has_pending, "discard cleared the pending data");
is($row->field('name'), 'theirs', "row now reads the refreshed value");
ok(lives { $row->save }, "save() is a no-op after discard");
};
subtest force_sync_then_save => sub {
my ($row, $pk) = desynced_row();
$row->force_sync;
ok(!$row->is_desynced, "force_sync cleared the desync flags");
ok($row->has_pending, "pending data is still staged");
ok(lives { $row->save }, "save() works after force_sync");
is(db_value(name => $pk), 'mine', "local edit overwrote the conflicting value");
};
subtest update_clears_desync_per_field => sub {
my ($row, $pk) = desynced_row();
ok(lives { $row->update({name => 'updated'}) }, "update() of the desynced field is an explicit overwrite");
ok(!$row->is_desynced, "no desync flags remain");
is(db_value(name => $pk), 'updated', "database has the updated value");
};
subtest update_of_other_field_still_croaks => sub {
my ($row, $pk) = desynced_row();
like(
( run in 0.455 second using v1.01-cache-2.11-cpan-f4a522933cf )