DBIx-QuickORM

 view release on metacpan or  search on metacpan

t/AI/transaction_extra.t  view on Meta::CPAN

        ok($con->current_txn, "current_txn set inside the action");
        is($con->current_txn, $t, "current_txn is the active txn object");
        ok(!$t->is_savepoint, "top-level txn is not a savepoint");
        is($t->state, 'active', "state is active inside the action");
        $con->handle('items')->insert({name => 'committed'});
    });

    ok(!$con->in_txn,      "in_txn false after commit");
    ok(!$con->current_txn, "current_txn cleared after commit");
    # state() is derived from result: success records 1, so it reports
    # 'committed' once the action returns normally.
    is($txn->state, 'committed', "state transitioned out of active to committed on implicit commit");
    ok($txn->committed, "committed is true");
    ok(!$txn->rolled_back, "rolled_back is false");
    is($txn->result, 1, "result is 1 after commit");

    is(disk_names(), ['committed'], "row persisted to disk after commit");
};

subtest rollback_on_die => sub {
    my $before = disk_names();

    my $err = dies {
        $con->txn(sub {
            $con->handle('items')->insert({name => 'doomed'});
            die "boom\n";
        });
    };

    like($err, qr/boom/, "exception propagated out of txn()");
    is(disk_names(), $before, "row gone from disk after rollback-on-die");
};

subtest savepoint_nesting => sub {
    my $before = disk_names();

    $con->txn(sub {
        my $outer = shift;
        ok(!$outer->is_savepoint, "outer txn is not a savepoint");

        $con->handle('items')->insert({name => 'outer_keep'});

        $con->txn(sub {
            my $inner = shift;
            ok($inner->is_savepoint, "nested txn is a savepoint");
            isnt($inner, $outer, "inner txn is a distinct object");
            $con->handle('items')->insert({name => 'inner_drop'});
            $inner->rollback;
        });

        $con->handle('items')->insert({name => 'outer_keep2'});
    });

    is(
        disk_names(),
        [sort(@$before, 'outer_keep', 'outer_keep2')],
        "outer changes persisted, inner savepoint rollback discarded only its row",
    );
};

subtest callbacks_on_commit => sub {
    my %seen;
    $con->txn(
        action        => sub { $seen{action}++ },
        on_success    => sub { $seen{success}++ },
        on_fail       => sub { $seen{fail}++ },
        on_completion => sub { $seen{completion}++ },
    );
    is(\%seen, {action => 1, success => 1, completion => 1}, "commit fires success+completion, not fail");
};

subtest callbacks_on_rollback => sub {
    my %seen;
    $con->txn(
        action        => sub { $seen{action}++; $_[0]->rollback },
        on_success    => sub { $seen{success}++ },
        on_fail       => sub { $seen{fail}++ },
        on_completion => sub { $seen{completion}++ },
    );
    is(\%seen, {action => 1, fail => 1, completion => 1}, "rollback fires fail+completion, not success");
};

subtest callbacks_added_to_object => sub {
    my %seen;
    $con->txn(sub {
        my $t = shift;
        $t->add_success_callback(sub { $seen{success}++ });
        $t->add_fail_callback(sub { $seen{fail}++ });
        $t->add_completion_callback(sub { $seen{completion}++ });
    });
    is(\%seen, {success => 1, completion => 1}, "add_*_callback success path");
};

subtest state_rolled_back => sub {
    my $txn = $con->txn(sub { $_[0]->rollback("nope") });
    is($txn->state, 'rolled_back', "state transitioned to rolled_back");
    is($txn->result, 0, "result is 0 after rollback");
    ok($txn->complete, "complete is true after rollback");
};

subtest destroy_rolls_back => sub {
    my $before = disk_names();

    my $warns = warnings {
        my $txn = $con->txn;
        ok($txn->isa('DBIx::QuickORM::Connection::Transaction'), "txn() with no action returns a live txn object");
        is($txn->state, 'active', "long-lived txn starts active");
        $con->handle('items')->insert({name => 'abandoned'});
        # $txn falls out of scope here while still active.
        $txn = undef;
    };

    ok(!$con->in_txn, "connection no longer in a txn after abandoned txn destroyed");
    is(disk_names(), $before, "abandoned active txn was rolled back on DESTROY");

    # DESTROY-driven rollback is a documented safety net and surfaces a
    # diagnostic noting that the transaction fell out of scope.
    ok(
        (grep { $_ =~ /fell out of scope/i } @$warns),
        "DESTROY rollback warns that the dropped txn fell out of scope",
    ) or diag(explain($warns));
};

subtest auto_retry_returns_value => sub {
    my $calls = 0;
    my $out   = $con->auto_retry(sub { $calls++; return 'the-result' });
    is($out, 'the-result', "auto_retry returns the callback result on success");
    is($calls, 1, "auto_retry ran the callback once on immediate success");
};

subtest auto_retry_in_txn_croaks => sub {
    my $err = dies {
        $con->txn(sub {
            $con->auto_retry(sub { 1 });
        });
    };
    like($err, qr/Cannot use auto_retry inside a transaction/, "auto_retry croaks inside an open txn");
};

subtest auto_retry_txn_persists => sub {
    my $before = disk_names();
    my $txn    = $con->auto_retry_txn(sub {
        $con->handle('items')->insert({name => 'via_retry'});



( run in 0.808 second using v1.01-cache-2.11-cpan-5b529ec07f3 )