App-DrivePlayer
view release on metacpan or search on metacpan
t/unit/Test/DrivePlayer/Player.pm view on Meta::CPAN
# Simulate stale token by backdating the token_time
$p->_token_time($p->_token_time - 4000);
$p->_bearer_token();
is $call_count, 2, 'auth->headers called again after token expires';
is $p->_token, 'Bearer token_call_2', 'new token returned after refresh';
}
sub bearer_token_clears_auth_cache : Tests(1) {
my ($self) = @_;
# Verify that {headers} is nil at the moment headers() is called, proving
# that the code does `delete $auth->{headers}` before calling headers().
my $was_nil_on_call;
my $auth = fake_auth(token => 'Bearer fresh_token');
$auth->{headers} = ['Authorization', 'Bearer stale_cached'];
$self->_mock('clear_check', 'Test::DrivePlayer::MockAuth', 'headers', sub {
my ($a) = @_;
$was_nil_on_call = !defined $a->{headers};
$a->{headers} = ['Authorization', $a->{token}];
return $a->{headers};
});
my $p = App::DrivePlayer::Player->new(auth => $auth);
$p->_token_time(time() - 4000);
$p->_token('Bearer stale');
$p->_bearer_token();
ok $was_nil_on_call, 'auth->{headers} cleared before re-fetching token';
}
# ---- State transitions ----
sub state_transitions : Tests(6) {
my ($self) = @_;
my @state_changes;
my $p = fake_player(
on_state_change => sub { push @state_changes, $_[0] }
);
is $p->state, 'stop', 'initial state stop';
# Manually drive state transitions without spawning mpv
$p->_set_state('play');
is $p->state, 'play', 'state set to play';
is $state_changes[-1], 'play', 'on_state_change fired for play';
$p->_set_state('pause');
is $p->state, 'pause', 'state set to pause';
is $state_changes[-1], 'pause', 'on_state_change fired for pause';
# Setting same state should not fire callback again
my $prev_count = scalar @state_changes;
$p->_set_state('pause');
is scalar @state_changes, $prev_count, 'on_state_change not fired for same state';
}
sub on_state_change_not_required : Tests(1) {
my ($self) = @_;
my $p = fake_player();
lives_ok { $p->_set_state('play') } '_set_state lives without on_state_change callback';
}
# ---- End-of-file handling ----
sub handle_end_file_eof : Tests(3) {
my ($self) = @_;
my $track_ended = 0;
my @state_changes;
my $p = fake_player(
on_track_end => sub { $track_ended++ },
on_state_change => sub { push @state_changes, $_[0] },
);
$p->_set_state('play');
$p->_handle_end_file({ event => 'end-file', reason => 'eof' });
is $p->state, 'stop', 'state becomes stop on eof';
is $track_ended, 1, 'on_track_end fired on eof';
is $state_changes[-1], 'stop', 'on_state_change fired with stop';
}
sub handle_end_file_error : Tests(2) {
my ($self) = @_;
# 'error' means the stream died mid-play (transient network failure,
# Drive 4xx/5xx, token expiry). We advance, same as 'eof'.
my $track_ended = 0;
my $p = fake_player(on_track_end => sub { $track_ended++ });
$p->_set_state('play');
$p->_handle_end_file({ event => 'end-file', reason => 'error' });
is $track_ended, 1, 'on_track_end fired for error reason';
is $p->state, 'stop', 'state becomes stop on error';
}
sub handle_end_file_stop_reason : Tests(2) {
my ($self) = @_;
# 'stop' fires for user-initiated stop AND for loadfile-replace
# pre-empting the current track. We must NOT advance on it, otherwise
# pressing Next / Stop would trigger phantom skips.
my $track_ended = 0;
my $p = fake_player(on_track_end => sub { $track_ended++ });
$p->_set_state('play');
$p->_handle_end_file({ event => 'end-file', reason => 'stop' });
is $track_ended, 0, 'on_track_end NOT fired for stop reason';
is $p->state, 'play', 'state unchanged for stop reason';
}
sub handle_end_file_quit_reason : Tests(2) {
my ($self) = @_;
# 'quit' is emitted when mpv itself is shutting down â ignore.
( run in 3.828 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )