Skip to content

Commit

Permalink
Allow syncing from the base state of 0.
Browse files Browse the repository at this point in the history
0 is the state you get when no entities have been created for a specific class
yet. If you then add an entity, the state will be '1'. getFooUpdates with a
sinceState of 0 should be able to calculate the changes for you.

0-0 is also valid for complex states.
  • Loading branch information
wolfsage committed Sep 9, 2016
1 parent a9e759e commit e716d72
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 38 deletions.
2 changes: 1 addition & 1 deletion lib/Ix/DBIC/Result.pm
Expand Up @@ -213,7 +213,7 @@ sub ix_compare_state ($self, $since, $state) {
}

if ($high_ms < $since) { return Ix::StateComparison->bogus; }
if ($low_ms >= $since) { return Ix::StateComparison->resync; }
if ($low_ms > $since) { return Ix::StateComparison->resync; }
if ($high_ms == $since) { return Ix::StateComparison->in_sync; }

return Ix::StateComparison->okay;
Expand Down
2 changes: 1 addition & 1 deletion lib/Ix/DBIC/ResultSet.pm
Expand Up @@ -158,7 +158,7 @@ sub ix_get_updates ($self, $ctx, $arg = {}) {

if ($statecmp->is_resync) {
$ctx->error(cannotCalculateChanges => {
description => "client cache must be reconstucted"
description => "client cache must be reconstructed"
})->throw
}

Expand Down
106 changes: 105 additions & 1 deletion t/basic.t
Expand Up @@ -354,7 +354,7 @@ subtest "invalid sinceState" => sub {

subtest "too low" => sub {
my $res = $jmap_tester->request([
[ getCookieUpdates => { sinceState => 1 } ],
[ getCookieUpdates => { sinceState => 0 } ],
]);

cmp_deeply(
Expand Down Expand Up @@ -1376,4 +1376,108 @@ subtest "ix_created test" => sub {
is("". $res->sentence(0)->arguments->{state}, $tstate, 'cake topper state unchanged');
};

{
$jmap_tester->_set_cookie('bakesaleUserId', $dataset{users}{alh});

# Check state, should be 0
my $res = $jmap_tester->request([
[ getCookies => {} ],
]);

is($res->single_sentence->arguments->{state}, 0, 'got state of 0')
or diag $res->as_stripped_struct;

# Ask for updates, should be told we are in sync
$res = $jmap_tester->request([
[ getCookieUpdates => {
sinceState => 0,
fetchRecords => \1,
fetchRecordProperties => [ qw(type) ],
}
],
]);

my $args = $res->single_sentence->arguments;
is($args->{newState}, 0, "newState is right");
is($args->{oldState}, 0, "oldState is right");
is_deeply($args->{changed}, [], 'no changes');

# Add some cookies, ensure ifInState works
$res = $jmap_tester->request([
[
setCookies => {
ifInState => 0,
create => {
yellow => { type => 'shortbread', },
gold => { type => 'anzac' },
},
},
],
]);

cmp_deeply(
$jmap_tester->strip_json_types( $res->as_pairs ),
[
[
cookiesSet => superhashof({
oldState => 0,
newState => 1,

created => {
yellow => superhashof({ id => ignore(), }),
gold => superhashof({ id => ignore() }),
},
}),
],
],
"we can create cookies with ifInState 0",
) or diag(explain($jmap_tester->strip_json_types( $res->as_pairs )));

my @created_ids = $res->single_sentence->as_set->created_ids;
is(@created_ids, 2, 'got two created ids');

# Verify updates
$res = $jmap_tester->request([
[ getCookieUpdates => { sinceState => '0' } ],
]);

cmp_deeply(
$res->as_stripped_struct->[0][1]{changed},
set(map { "$_" } @created_ids),
"getCookieUpdates with state of 0 works"
);

# If our sinceState is too low we should get a resync
$res = $jmap_tester->request([
[ getCookieUpdates => {
sinceState => -1,
fetchRecords => \1,
fetchRecordProperties => [ qw(type) ],
}
],
]);

jcmp_deeply(
$res->as_stripped_struct->[0][1],
{
description => 'client cache must be reconstructed',
type => 'cannotCalculateChanges'
},
"Got resync error with too low sinceState"
) or diag explain $res->as_stripped_struct;

# Complex ones too
$res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => '0-0' }, ],
]);

$args = $res->single_sentence->arguments;
is($args->{newState}, "0-0", "newState is right");
is($args->{oldState}, "0-0", "oldState is right");
is_deeply($args->{changed}, [], 'no changes');

# Put this back
$jmap_tester->_set_cookie('bakesaleUserId', $dataset{users}{rjbs});
}

done_testing;
16 changes: 13 additions & 3 deletions t/lib/Bakesale.pm
Expand Up @@ -48,7 +48,9 @@ package Bakesale::Test {
modSeqChanged => 1,
});

return $user1->id;
$user1 = $user_rs->single({ id => $user1->id });

return ($user1->id, $user1->datasetId);
}

sub load_trivial_dataset ($self, $schema) {
Expand All @@ -72,6 +74,14 @@ package Bakesale::Test {

$user2 = $user_rs->single({ id => $user2->id });

my $user3 = $user_rs->create({
datasetId => \q{pseudo_encrypt(nextval('key_seed_seq')::int)},
username => 'alh',
modseq(1)
});

$user3 = $user_rs->single({ id => $user3->id });

my $a1 = $user1->datasetId;
my $a2 = $user2->datasetId;

Expand Down Expand Up @@ -102,8 +112,8 @@ package Bakesale::Test {
]);

return {
datasets => { rjbs => $a1, neilj => $a2 },
users => { rjbs => $user1->id, neilj => $user2->id },
datasets => { rjbs => $a1, neilj => $a2, alh => $user3->datasetId, },
users => { rjbs => $user1->id, neilj => $user2->id, alh => $user3->id, },
recipes => { 1 => $recipes[0]->id },
cookies => { map {; ($_+1) => $cookies[$_]->id } keys @cookies },
};
Expand Down
2 changes: 1 addition & 1 deletion t/lib/Bakesale/Schema/Result/Cake.pm
Expand Up @@ -64,7 +64,7 @@ sub ix_compare_state ($self, $since, $state) {
return Ix::StateComparison->bogus;
}

if ($cake_low >= $cake_since || $recipe_low >= $recipe_since) {
if ($cake_low > $cake_since || $recipe_low > $recipe_since) {
return Ix::StateComparison->resync;
}

Expand Down
2 changes: 1 addition & 1 deletion t/processor/basic.t
Expand Up @@ -231,7 +231,7 @@ subtest "invalid sinceState" => sub {

subtest "too low" => sub {
my $res = $ctx->process_request([
[ getCookieUpdates => { sinceState => 1 }, 'a' ],
[ getCookieUpdates => { sinceState => 0 }, 'a' ],
]);

cmp_deeply(
Expand Down
64 changes: 34 additions & 30 deletions t/updates.t
Expand Up @@ -11,9 +11,16 @@ use Test::Deep;
use Test::More;

my ($app, $jmap_tester) = Bakesale::Test->new_test_app_and_tester;
my $admin_id = Bakesale::Test->load_single_user($app->processor->schema_connection);
my ($admin_id, $datasetId) = Bakesale::Test->load_single_user($app->processor->schema_connection);
$jmap_tester->_set_cookie('bakesaleUserId', $admin_id);

# Set our base state to 1-1 so we can ensure we're told to resync if we
# pass in a sinceState lower than that (0-1 or 1-0 for example).
$app->processor->schema_connection->resultset('State')->populate([
{ datasetId => $datasetId, type => 'cakes', lowestModSeq => 1, highestModSeq => 1, },
{ datasetId => $datasetId, type => 'cakeRecipes', lowestModSeq => 1, highestModSeq => 1 },
]);

subtest "simple state comparisons" => sub {
# First up, we are going to set up fudge distinct states, each with 10
# updates. -- rjbs, 2016-05-03
Expand Down Expand Up @@ -50,10 +57,7 @@ subtest "simple state comparisons" => sub {
[ getCookieUpdates => { sinceState => "0" } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'error', 'can not sync from "0" state');

is($arg->{type}, "cannotCalculateChanges", "error type");
ok($res->as_struct->[0][1]{changed}->@*, 'can sync from "0" state');
};

subtest "synchronize from the future" => sub {
Expand Down Expand Up @@ -168,7 +172,7 @@ subtest "complex state comparisons" => sub {
$recipe_id{$n} = $recipe_res->{created}{$n}{id};

my $cr_state = $cr_res->single_sentence->as_set->new_state . "";
is($cr_state, $n, "after creating recipe $n, state is $n");
is($cr_state, $n+1, "after creating recipe $n, state is " . ($n + 1));
}
};

Expand Down Expand Up @@ -199,21 +203,21 @@ subtest "complex state comparisons" => sub {
}

my $state = $last_set_res->single_sentence->as_set->new_state . "";
is($state, "4-5", "four cake sets on recipe state 5; state is 4-5");
is($state, "5-6", "four cake sets on recipe state 5; state is 5-6");
};

my %cake_id_rev = reverse %cake_id;

subtest "synchronize to current state: no-op" => sub {
my $res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => "4-5" } ]
[ getCakeUpdates => { sinceState => "5-6" } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'cakeUpdates', 'cake updates!!');

is($arg->{oldState}, '4-5', "old state: 4-5");
is($arg->{newState}, '4-5', "new state: 4-5");
is($arg->{oldState}, '5-6', "old state: 5-6");
is($arg->{newState}, '5-6', "new state: 5-6");
ok( ! $arg->{hasMoreUpdates}, "no more updates");
ok(! $arg->{changed}->@*, "no items changed");
ok(! $arg->{removed}->@*, "no items removed");
Expand Down Expand Up @@ -252,16 +256,16 @@ subtest "complex state comparisons" => sub {
is($arg->{type}, "cannotCalculateChanges", "error type");
};

subtest "synchronize (3-5 to 4-5), no maxChanges" => sub {
subtest "synchronize (4-6 to 5-6), no maxChanges" => sub {
my $res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => "3-5" } ]
[ getCakeUpdates => { sinceState => "4-6" } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'cakeUpdates', 'cake updates!!');

is($arg->{oldState}, '3-5', "old state: 3-5");
is($arg->{newState}, '4-5', "new state: 4-5");
is($arg->{oldState}, '4-6', "old state: 4-6");
is($arg->{newState}, '5-6', "new state: 5-6");
ok( ! $arg->{hasMoreUpdates}, "no more updates");
is($arg->{changed}->@*, 5, "5 items changed");

Expand All @@ -274,16 +278,16 @@ subtest "complex state comparisons" => sub {
ok(! $arg->{removed}->@*, "no items removed");
};

subtest "synchronize (4-4 to 4-5), no maxChanges" => sub {
subtest "synchronize (5-5 to 5-6), no maxChanges" => sub {
my $res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => "4-4" } ]
[ getCakeUpdates => { sinceState => "5-5" } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'cakeUpdates', 'cake updates!!');

is($arg->{oldState}, '4-4', "old state: 4-4");
is($arg->{newState}, '4-5', "new state: 4-5");
is($arg->{oldState}, '5-5', "old state: 5-5");
is($arg->{newState}, '5-6', "new state: 5-6");
ok( ! $arg->{hasMoreUpdates}, "no more updates");
is($arg->{changed}->@*, 4, "4 items changed");

Expand All @@ -297,20 +301,20 @@ subtest "complex state comparisons" => sub {
};

for my $test (
[ "sync (3-4 to 4-5), no maxChanges", {} ],
[ "sync (3-4 to 4-5), maxChanges exceeds updates", { maxChanges => 10 } ],
[ "sync (3-4 to 4-5), maxChanges qeuals updates", { maxChanges => 8 } ],
[ "sync (4-5 to 5-6), no maxChanges", {} ],
[ "sync (4-5 to 5-6), maxChanges exceeds updates", { maxChanges => 10 } ],
[ "sync (4-5 to 5-6), maxChanges qeuals updates", { maxChanges => 8 } ],
) {
subtest $test->[0] => sub {
my $res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => "3-4", $test->[1]->%* } ]
[ getCakeUpdates => { sinceState => "4-5", $test->[1]->%* } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'cakeUpdates', 'cake updates!!');

is($arg->{oldState}, '3-4', "old state: 3-4");
is($arg->{newState}, '4-5', "new state: 4-5");
is($arg->{oldState}, '4-5', "old state: 4-5");
is($arg->{newState}, '5-6', "new state: 5-6");
ok( ! $arg->{hasMoreUpdates}, "no more updates");
is($arg->{changed}->@*, 8, "8 items changed");

Expand All @@ -325,18 +329,18 @@ subtest "complex state comparisons" => sub {
};
}

subtest "sync (3-4 to 4-5), maxChanges forces 2 passes" => sub {
subtest "sync (4-5 to 5-6), maxChanges forces 2 passes" => sub {
my %changed;
my $mid_state;
subtest "first pass at small-window update" => sub {
my $res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => "3-4", maxChanges => 5 } ]
[ getCakeUpdates => { sinceState => "4-5", maxChanges => 5 } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'cakeUpdates', 'cake updates!!');

is($arg->{oldState}, '3-4', "old state: 3-4");
is($arg->{oldState}, '4-5', "old state: 4-5");
ok(
$arg->{newState} ne $arg->{oldState},
"new state: $arg->{newState}",
Expand Down Expand Up @@ -377,18 +381,18 @@ subtest "complex state comparisons" => sub {
);
};

subtest "sync (3-4 to 4-5), maxChanges smaller than first window" => sub {
subtest "sync (4-5 to 5-6), maxChanges smaller than first window" => sub {
my %changed;
my $mid_state;
subtest "first pass at small-window update" => sub {
my $res = $jmap_tester->request([
[ getCakeUpdates => { sinceState => "3-4", maxChanges => 3 } ]
[ getCakeUpdates => { sinceState => "4-5", maxChanges => 3 } ]
]);

my ($type, $arg) = $res->single_sentence->as_struct->@*;
is($type, 'cakeUpdates', 'cake updates!!');

is($arg->{oldState}, '3-4', "old state: 3-4");
is($arg->{oldState}, '4-5', "old state: 4-5");
ok(
$arg->{newState} ne $arg->{oldState},
"new state: $arg->{newState}",
Expand Down

0 comments on commit e716d72

Please sign in to comment.