use strict; use warnings; use UR; use IO::File; use Test::More tests => 57; UR::Object::Type->define( class_name => 'Circle', has => [ radius => { is => 'Number', default_value => 1, }, ], ); sub add_test_observer { my ($aspect, $context, $observer_ran_ref) = @_; $$observer_ran_ref = 0; my $observer; my $callback; $callback = sub { $$observer_ran_ref = 1; }; $observer = $context->add_observer( aspect => $aspect, callback => $callback, ); unless ($observer) { die "Failed to add $aspect observer!"; } return $observer; } # Create a Circle my $circle = Circle->create(); ok($circle->isa('Circle'), 'create a circle'); ok($circle->radius == 1, 'default radius is 1'); # Verify Transaction Rollback Removes Observer and its Subscription # making sure if someone tries to catch their observer's delete that it runs # before the observer's self-created delete subscription { my $ran_observer_observer = 0; my $circle_trans = UR::Context::Transaction->begin(); ok($circle_trans, 'begin transaction'); my $ran_circle_radius_observer = 0; my $circle_obs = $circle->add_observer( aspect => 'radius', callback => sub { $ran_circle_radius_observer = 1; }, ); my $circle_obs_id = $circle_obs->id; my $ran_circle_obs_delete_obs = 0; my $subscription = $circle_obs->class->create_subscription( id => $circle_obs->id, method => 'delete', callback => sub { $ran_circle_obs_delete_obs = 1; }, note => "$circle_obs", ); my $observer_observer = UR::Observer->get(subject_class_name => 'UR::Observer', subject_id => $subscription->[1]); ok($circle_obs->isa('UR::Observer'), 'added an observer on the circle'); is(UR::Observer->get(subject_class_name => 'Circle', subject_id => $circle->id, aspect => 'radius'), $circle_obs, 'Can get the observer on the circle with get()'); my $circle_sub = $UR::Context::all_change_subscriptions->{Circle}->{radius}->{$circle->id}; ok($circle_sub, 'adding observer inserted a callback into the Context data structure for callbacks'); is(UR::Observer->get(subject_class_name => 'UR::Observer', subject_id => $circle_obs->id, aspect => 'delete'), $observer_observer, 'Can get the observer on the original observer deletion with get()'); ok($circle_trans->rollback(), 'rolled back transaction'); ok($ran_circle_obs_delete_obs == 0, 'rollback did not run the delete observer'); # because it's creation was undone before the radius observer was deleted $circle_sub = $UR::Context::all_change_subscriptions->{Circle}->{radius}->{$circle->id}; ok(!$circle_sub, 'rolling back transaction (and with it the observer) removed the subscription'); ok($circle_obs->isa('UR::DeletedRef'), 'radius observer is now a DeletedRef'); ok(! UR::Observer->get(subject_class_name => 'Circle', subject_id => $circle->id, aspect => 'radius'), 'get() no longer returns the circle observer'); ok(! UR::Observer->get(subject_class_name => 'UR::Observer', subject_id => $circle_obs_id, aspect => 'delete'), 'get() no longer returns the observer observer'); $ran_circle_obs_delete_obs = 0; $circle->radius(1); is($ran_circle_obs_delete_obs, 0, 'The circle radius observer did not run'); }; # Verify Transaction Rollback Observer Runs { $circle->radius(3); ok($circle->radius == 3, "original radius is three"); my $transaction = UR::Context::Transaction->begin(); my $observer_ran = 0; add_test_observer('rollback', $transaction, \$observer_ran); my $sub = $UR::Context::all_change_subscriptions->{'UR::Context::Transaction'}->{rollback}->{$transaction->id}; ok($sub, 'adding observer also create change subscription'); ok($transaction->isa('UR::Context::Transaction'), "created first transaction (to test rollback observer)"); ok(!$observer_ran, "observer rollback flag reset to 0"); $circle->radius(5); ok($circle->radius == 5, "in transaction (rollback test), radius is five"); ok($transaction->rollback(), "ran transaction rollback"); ok($observer_ran, "rollback observer ran successfully"); ok($circle->radius == 3, "after rollback, radius is three"); }; # Verify Transaction Commit Observer Runs { $circle->radius(4); ok($circle->radius == 4, "original radius (commit test) is four"); my $transaction = UR::Context::Transaction->begin(); my $observer_ran = 0; add_test_observer('commit', $transaction, \$observer_ran); ok($transaction->isa('UR::Context::Transaction'), "created second transaction (to test commit observer)"); ok(!$observer_ran, "observer rollback flag reset to 0"); $circle->radius(6); ok($circle->radius == 6, "in transaction (commit test), radius is six"); ok($transaction->commit(), "ran transaction commit"); ok($observer_ran, "commit observer ran successfully"); ok($circle->radius == 6, "after commit, radius is six"); # Trying to Rollback a Committed Transaction Fails ok($transaction->state eq 'committed', "transaction is already committed"); my $rv= eval {$transaction->rollback()} || 0; ok($rv == 0, "properly failed transaction rollback for already committed transaction"); }; # Test Nested Transactions { $circle->radius(3); ok($circle->radius == 3, "original radius is 3"); my $outer_transaction = UR::Context::Transaction->begin(); my $outer_observer_ran = 0; add_test_observer('rollback', $outer_transaction, \$outer_observer_ran); ok($outer_transaction->isa('UR::Context::Transaction'), "created outer transaction"); ok(!$outer_observer_ran, "outer observer flag reset to 0"); $circle->radius(5); ok($circle->radius == 5, "in outer transaction, radius is 5"); my $inner_transaction = UR::Context::Transaction->begin(); my $inner_observer_ran = 0; add_test_observer('rollback', $inner_transaction, \$inner_observer_ran); ok($inner_transaction->isa('UR::Context::Transaction'), "created inner transaction"); ok(!$inner_observer_ran, "inner observer flag reset to 0"); $circle->radius(7); ok($circle->radius == 7, "in inner transaction, radius is 7"); ok($inner_transaction->rollback(), "ran inner transaction rollback"); ok($inner_observer_ran, "inner transaction observer ran successfully"); ok($circle->radius == 5, "after inner transaction rollback, radius is 5"); ok($outer_transaction->rollback(), "ran transaction rollback"); ok($outer_observer_ran, "outer transaction observer ran successfully"); ok($circle->radius == 3, "after rollback, radius is 3"); }; # testing inner commit { $circle->radius(4); ok($circle->radius == 4, "original radius is 4"); my $outer_transaction = UR::Context::Transaction->begin(); my $outer_observer_ran = 0; add_test_observer('rollback', $outer_transaction, \$outer_observer_ran); ok($outer_transaction->isa('UR::Context::Transaction'), "created outer transaction"); ok(!$outer_observer_ran, "outer observer flag reset to 0"); $circle->radius(6); ok($circle->radius == 6, "in outer transaction, radius is 6"); my $inner_transaction = UR::Context::Transaction->begin(); my $inner_observer_ran = 0; add_test_observer('commit', $inner_transaction, \$inner_observer_ran); ok($inner_transaction->isa('UR::Context::Transaction'), "created inner transaction"); ok(!$inner_observer_ran, "inner observer flag reset to 0"); $circle->radius(8); ok($circle->radius == 8, "in inner transaction, radius is 8"); ok($inner_transaction->commit(), "ran inner transaction commit"); ok($inner_observer_ran, "inner transaction observer ran successfully"); ok($circle->radius == 8, "after inner transaction commit, radius is 8"); ok($outer_transaction->rollback(), "ran transaction rollback"); ok($outer_observer_ran, "outer transaction observer ran successfully"); ok($circle->radius == 4, "after rollback, radius is 4"); }; done_testing(); 1;