116 lines
4.0 KiB
PHP
116 lines
4.0 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace BlaxSoftware\LaravelWebSockets\Test\Websocket;
|
||
|
|
|
||
|
|
use BlaxSoftware\LaravelWebSockets\Websocket\ChildMessageOutcome;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The parent answers for a message child that died without replying (#1462),
|
||
|
|
* and only then: never after a real reply, never for a clean exit, and never
|
||
|
|
* before the child's last frames have been read.
|
||
|
|
*/
|
||
|
|
class ChildMessageOutcomeTest extends TestCase
|
||
|
|
{
|
||
|
|
private const EXIT_OK = 0; // exit(0)
|
||
|
|
private const EXIT_FATAL = 255 << 8; // exit(255), what PHP does after a fatal error
|
||
|
|
private const KILLED = 9; // terminated by SIGKILL
|
||
|
|
|
||
|
|
private array $crashes = [];
|
||
|
|
|
||
|
|
private function outcome(string $event = 'atc-traffic.subscribe[x1]'): ChildMessageOutcome
|
||
|
|
{
|
||
|
|
$this->crashes = [];
|
||
|
|
|
||
|
|
return new ChildMessageOutcome($event, function (int $status) {
|
||
|
|
$this->crashes[] = $status;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_killed_child_without_reply_is_answered_once_both_signals_are_in()
|
||
|
|
{
|
||
|
|
foreach ([[true, false], [false, true]] as [$streamFirst]) {
|
||
|
|
$outcome = $this->outcome();
|
||
|
|
$outcome->frameReceived('{"event":"atc-traffic.subscribe[x1]:progress","data":[]}');
|
||
|
|
|
||
|
|
if ($streamFirst) {
|
||
|
|
$outcome->streamClosed();
|
||
|
|
$this->assertSame([], $this->crashes, 'No verdict before the exit status is known.');
|
||
|
|
$outcome->exited(self::KILLED);
|
||
|
|
} else {
|
||
|
|
$outcome->exited(self::KILLED);
|
||
|
|
$this->assertSame([], $this->crashes, 'No verdict while frames may still be unread.');
|
||
|
|
$outcome->streamClosed();
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->assertSame([self::KILLED], $this->crashes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_fatal_exit_without_reply_is_answered()
|
||
|
|
{
|
||
|
|
$outcome = $this->outcome();
|
||
|
|
$outcome->streamClosed();
|
||
|
|
$outcome->exited(self::EXIT_FATAL);
|
||
|
|
|
||
|
|
$this->assertSame([self::EXIT_FATAL], $this->crashes);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_child_that_replied_is_left_alone_even_if_it_crashed_afterwards()
|
||
|
|
{
|
||
|
|
foreach (['response', 'error'] as $suffix) {
|
||
|
|
$outcome = $this->outcome();
|
||
|
|
$outcome->frameReceived('{"event":"atc-traffic.subscribe[x1]:' . $suffix . '","data":{"ok":true},"channel":"websocket"}');
|
||
|
|
$outcome->streamClosed();
|
||
|
|
$outcome->exited(self::EXIT_FATAL);
|
||
|
|
|
||
|
|
$this->assertTrue($outcome->replied());
|
||
|
|
$this->assertSame([], $this->crashes, "A {$suffix} frame counts as the reply.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_frames_for_other_events_or_broadcasts_are_not_a_reply()
|
||
|
|
{
|
||
|
|
$outcome = $this->outcome('dashboard.index');
|
||
|
|
$outcome->frameReceived('B:{"event":"dashboard.index:response"}');
|
||
|
|
$outcome->frameReceived('{"event":"dashboard.indexer:response"}');
|
||
|
|
$outcome->frameReceived('{"data":1,"event":"dashboard.index:response"}');
|
||
|
|
$outcome->frameReceived(null);
|
||
|
|
|
||
|
|
$this->assertFalse($outcome->replied());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_event_names_are_matched_in_their_json_encoded_form()
|
||
|
|
{
|
||
|
|
$event = 'admin/user.show[a"b]';
|
||
|
|
$outcome = $this->outcome($event);
|
||
|
|
$outcome->frameReceived(json_encode(['event' => $event . ':response', 'data' => null]));
|
||
|
|
|
||
|
|
$this->assertTrue($outcome->replied());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_clean_exit_or_an_unknown_status_sends_nothing()
|
||
|
|
{
|
||
|
|
foreach ([self::EXIT_OK, null] as $status) {
|
||
|
|
$outcome = $this->outcome();
|
||
|
|
$outcome->streamClosed();
|
||
|
|
$outcome->exited($status);
|
||
|
|
|
||
|
|
$this->assertSame([], $this->crashes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_the_verdict_is_given_only_once()
|
||
|
|
{
|
||
|
|
$outcome = $this->outcome();
|
||
|
|
$outcome->streamClosed();
|
||
|
|
$outcome->exited(self::KILLED);
|
||
|
|
$outcome->streamClosed();
|
||
|
|
$outcome->exited(self::KILLED);
|
||
|
|
|
||
|
|
$this->assertCount(1, $this->crashes);
|
||
|
|
}
|
||
|
|
}
|