EV-Telegram-TDLib
view release on metacpan or search on metacpan
t/45_required_args.t view on Meta::CPAN
['import_contacts phone',
sub { $td->import_contacts([{ phone => 79991234567,
first_name => 'A' }], sub {}) }],
);
for my $case (@string_cases) {
my ($label, $code) = @$case;
@sent = ();
# asserted, not skipped: a case that croaks or sends nothing used to
# vanish silently, so one of them tested nothing for several rounds
ok eval { $code->(); 1 }, "$label runs" or diag $@;
ok scalar(@sent), "$label sent a request";
next unless @sent;
my $req = Cpanel::JSON::XS->new->utf8->decode($sent[-1]);
delete $req->{'@extra'};
my $r = EV::Telegram::TDLib->execute($req);
unlike $r->{message} // '', qr/Failed to parse/,
"$label built from a number still parses";
}
}
# --- a text field must carry a JSON String even when the caller's scalar has
# been used as a number. Cpanel::JSON::XS emits a Number as soon as the SV
# has IOK, and a bare numeric comparison sets it -- so the same variable
# could send once and be rejected the second time.
{
my @text_cases = (
['send_message', sub { $td->send_message(-100, 42, sub {}) }],
['edit_message', sub { $td->edit_message(-100, 5, 42, sub {}) }],
['send_poll', sub { $td->send_poll(-100, 42, [1, 2], sub {}) }],
['ask prompt', sub { $td->ask(-100, 7, 42, sub {}) }],
);
for my $case (@text_cases) {
my ($label, $code) = @$case;
@sent = ();
ok eval { $code->(); 1 }, "$label runs" or diag $@;
ok scalar(@sent), "$label sent a request";
next unless @sent;
my $req = Cpanel::JSON::XS->new->utf8->decode($sent[-1]);
delete $req->{'@extra'};
my $r = EV::Telegram::TDLib->execute($req);
unlike $r->{message} // '', qr/Failed to parse|Expected String/,
"$label accepts a numeric value in its text";
}
# the action-at-a-distance case: a string that only later gets compared.
# Key order is randomised per hash, so compare the encoded value, not the
# whole document.
my $text = "42";
@sent = ();
$td->send_message(-100, $text, sub {});
like $sent[-1], qr/"text":"42"/, 'a numeric-looking string sends as a String';
if ($text > 10) { } # nothing but a comparison
@sent = ();
$td->send_message(-100, $text, sub {});
like $sent[-1], qr/"text":"42"/,
'and still does after the caller compares it numerically';
unlike $sent[-1], qr/"text":42\b/, 'never as a bare JSON Number';
}
# --- the login credentials are string slots too. A phone number written
# without quotes is the obvious way to hit this.
{
my $wait_phone = q({"@type":"updateAuthorizationState",)
. q("authorization_state":{"@type":"authorizationStateWaitPhoneNumber"}});
@sent = ();
my $c = EV::Telegram::TDLib->new(
api_id => 1, api_hash => 'x', phone_number => 79991234567,
database_directory => 't/tmp-authstr', on_error => sub {});
$c->inject_raw($wait_phone);
my ($req) = grep { /setAuthenticationPhoneNumber/ } @sent;
ok $req, 'the phone number was sent';
like $req, qr/"phone_number":"79991234567"/,
'an unquoted phone number still crosses as a JSON String';
}
# --- a dying bool or "" overload must not escape guarded either. The C
# drain was fixed for this; the Perl guard is its sibling, and an escape
# there also clears $@ so nothing reports it.
{
for my $kind (qw(stringify boolify)) {
my $pkg = "GuardNasty\u$kind";
my $body = $kind eq 'stringify'
? q{use overload '""' => sub { die "boom\n" }, fallback => 0;}
: q{use overload 'bool' => sub { die "boom\n" },
'""' => sub { 'nasty' }, fallback => 0;};
eval "package $pkg; $body sub new { bless {}, shift } 1" or die $@;
my @errs;
my $c = EV::Telegram::TDLib->new(
api_id => 1, api_hash => 'x', database_directory => 't/tmp-guard',
on_error => sub { push @errs, $_[0] });
my $escaped = 0;
eval { $c->guarded(sub { die $pkg->new }); 1 } or $escaped = 1;
ok !$escaped, "a dying $kind overload does not escape guarded";
is scalar(@errs), 1, "and is still reported once";
}
}
# --- the reporting path itself must not be able to escape. An on_error that
# dies is the last place left to throw from, and guarded runs during update
# dispatch, where an escape unwinds out of the handler.
{
my $c = EV::Telegram::TDLib->new(
api_id => 1, api_hash => 'x', database_directory => 't/tmp-onerrdie',
on_error => sub { die "on_error itself died\n" });
my $escaped = '';
eval { $c->guarded(sub { die "inner\n" }); 1 } or $escaped = $@;
is $escaped, '', 'a dying on_error does not escape guarded either';
}
# --- with no on_error at all the report has to go somewhere: it warns, which
# is the only channel left, and losing it would make a dying callback silent
{
my $c = EV::Telegram::TDLib->new(
api_id => 1, api_hash => 'x', database_directory => 't/tmp-nowarn');
my @warns;
my $escaped = '';
{
local $SIG{__WARN__} = sub { push @warns, $_[0] };
eval { $c->guarded(sub { die "unheard\n" }); 1 } or $escaped = $@;
( run in 0.908 second using v1.01-cache-2.11-cpan-3f962061493 )