EV-Nats

 view release on metacpan or  search on metacpan

t/18_object_kv.t  view on Meta::CPAN


subtest 'nats.go-style delete marker reads as missing (no KV-Operation header)' => sub {
    plan tests => 4;
    my $js = MockJS->new;
    my $os = EV::Nats::ObjectStore->new(js => $js, bucket => 'B');
    # A marker exactly as nats.go writes it: deleted:true meta, no headers.
    my $marker = {
        name => 'gone.txt', bucket => 'B', nuid => '',
        size => 0, chunks => 0, digest => '', deleted => JSON::PP::true(),
    };
    my $meta_subj = '$O.B.M.' . EV::Nats::ObjectStore::_encode_name('gone.txt');
    $js->{store}{$meta_subj} =
        { data => encode_base64(JSON::PP::encode_json($marker), ''), seq => ++$js->{seq} };
    push @{ $js->{order} }, $meta_subj;

    my ($got, $inf);
    $os->get('gone.txt', sub { $got = [ @_ ] });
    is $got->[0], undef, 'get returns no data for a deleted:true marker';
    is $got->[1], undef, 'get returns no error';
    $os->info('gone.txt', sub { $inf = [ @_ ] });
    is $inf->[0], undef, 'info returns no meta';
    is $inf->[1], undef, 'info returns no error';
};

subtest 'overwrite purges the previous object chunks' => sub {
    plan tests => 5;
    my $js = MockJS->new;
    my $os = EV::Nats::ObjectStore->new(js => $js, bucket => 'B');
    $os->put('file', 'first', sub { });
    $js->drain;
    my ($meta_subj) = grep { /\.M\./ } keys %{ $js->{store} };
    my $nuid1 = (JSON::PP::decode_json(decode_base64($js->{store}{$meta_subj}{data})))->{nuid};

    $js->{msg_gets} = 0;
    my $info;
    $os->put('file', 'second', sub { $info = [ @_ ] });
    $js->drain;
    is $info->[1], undef, 'second put succeeded';
    ok $js->{msg_gets} >= 1, 'second put looked up the existing meta first';
    my $nuid2 = (JSON::PP::decode_json(decode_base64($js->{store}{$meta_subj}{data})))->{nuid};
    isnt $nuid2, $nuid1, 'overwrite used a fresh nuid';
    ok((grep { $_ eq '$O.B.C.' . $nuid1 } @{ $js->{purges} || [] }),
       'previous nuid chunk subject purged');
    ok !exists $js->{store}{ '$O.B.C.' . $nuid1 }, 'old chunks gone from the store';
};

subtest 'list decodes base64url and legacy names' => sub {
    plan tests => 2;
    my $js = MockJS->new;
    my $os = EV::Nats::ObjectStore->new(js => $js, bucket => 'B');
    $os->put('report.txt', 'x', sub { });
    $js->drain;
    # plus a legacy-encoded name, as 0.05 would have written it
    my $legacy_meta = { name => 'a b.txt', bucket => 'B', nuid => 'N',
                        size => 0, chunks => 0, digest => '' };
    $js->{store}{'$O.B.M.a%20b.txt'} =
        { data => encode_base64(JSON::PP::encode_json($legacy_meta), ''), seq => ++$js->{seq} };
    push @{ $js->{order} }, '$O.B.M.a%20b.txt';
    my $names;
    $os->list(sub { $names = $_[0] });
    ok((grep { $_ eq 'report.txt' } @$names), 'base64url name decoded in list');
    ok((grep { $_ eq 'a b.txt' } @$names), 'legacy %XX name decoded in list');
};

subtest 'get() does not pin the connection' => sub {
    plan tests => 2;
    my $destroyed = 0;
    my $r;
    {
        my $js = MockJS->new;
        my $os = EV::Nats::ObjectStore->new(js => $js, bucket => 'B');
        # rides along inside the $self that get()'s closure captures
        $os->{_guard} = Guard->new(sub { $destroyed = 1 });
        $os->put('g', 'payload', sub { });
        $js->drain;
        $os->get('g', sub { $r = [ @_ ] });
        is $r->[0], 'payload', 'get returned data';
    }
    ok $destroyed, 'ObjectStore freed after get() (callback cycle broken)';
};

subtest 'missing object is a clean miss' => sub {
    plan tests => 2;
    my $js = MockJS->new;
    my $os = EV::Nats::ObjectStore->new(js => $js, bucket => 'B');
    my $got;
    $os->get('nope', sub { $got = [ @_ ] });
    is $got->[0], undef, 'get(missing) returns no data';
    is $got->[1], undef, 'get(missing) returns no error';
};

subtest 'KV key and bucket validation' => sub {
    plan tests => 12;
    my $js = MockJS->new;
    ok !eval { EV::Nats::KV->new(js => $js, bucket => 'bad bucket'); 1 },
       'bucket with a space rejected';
    my $kv = eval { EV::Nats::KV->new(js => $js, bucket => 'good-bucket_1') };
    ok $kv, 'valid bucket accepted';
    for my $bad ('a b', 'a>b', 'a*b', '.lead', 'trail.', "nl\nkey") {
        (my $show = $bad) =~ s/\n/\\n/;
        ok !eval { $kv->get($bad, sub { }); 1 }, "key '$show' rejected";
    }
    for my $good ('a.b.c', 'A_b-c', 'x=1', 'path/to/key') {
        ok eval { $kv->get($good, sub { }); 1 }, "key '$good' accepted"
            or diag $@;
    }
};

done_testing;



( run in 0.827 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )