create-prisma-php-app 5.1.0-alpha.23 → 5.1.0-alpha.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,277 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Tests;
6
+
7
+ use Lib\Auth\Auth;
8
+ use Lib\Websocket\ConnectionManager;
9
+ use Lib\Websocket\Socket;
10
+ use Lib\Websocket\SocketRegistry;
11
+ use PHPUnit\Framework\TestCase;
12
+ use Tests\Support\FakeConnection;
13
+ use Tests\Support\RequiresFeature;
14
+
15
+ /**
16
+ * The named-socket wire from the server's side: where a connection must go,
17
+ * what the first frame is, and which failures travel as the reserved
18
+ * `{"error": ...}` frame.
19
+ *
20
+ * Mirrors the client runtime's SocketClient tests: name in the `name` query
21
+ * parameter, arguments as the first JSON object frame, refusals as an error
22
+ * frame followed by a close.
23
+ */
24
+ final class ConnectionManagerTest extends TestCase
25
+ {
26
+ use RequiresFeature;
27
+
28
+ /** @var array<string, string|false> */
29
+ private array $envBackup = [];
30
+
31
+ protected function setUp(): void
32
+ {
33
+ $this->requireFeature('websocket');
34
+ SocketRegistryTest::resetRegistry();
35
+ }
36
+
37
+ protected function tearDown(): void
38
+ {
39
+ SocketRegistryTest::resetRegistry();
40
+
41
+ foreach ($this->envBackup as $name => $previous) {
42
+ if ($previous === false) {
43
+ putenv($name);
44
+ } else {
45
+ putenv("$name=$previous");
46
+ }
47
+ }
48
+ $this->envBackup = [];
49
+ }
50
+
51
+ private function setEnv(string $name, string $value): void
52
+ {
53
+ if (!array_key_exists($name, $this->envBackup)) {
54
+ $this->envBackup[$name] = getenv($name);
55
+ }
56
+ putenv("$name=$value");
57
+ }
58
+
59
+ /** A connection from a dev-browser origin, upgraded and args sent. */
60
+ private function openWithArgs(
61
+ ConnectionManager $manager,
62
+ FakeConnection $conn,
63
+ array $args = [],
64
+ ): void {
65
+ $manager->onOpen($conn);
66
+ $manager->onMessage($conn, json_encode($args === [] ? new \stdClass() : $args));
67
+ }
68
+
69
+ private function devConnection(string $name): FakeConnection
70
+ {
71
+ return new FakeConnection(
72
+ "/__pulsepoint/ws?name=$name",
73
+ ['Origin' => 'http://localhost:3000'],
74
+ );
75
+ }
76
+
77
+ public function testUnknownNameIsRefusedWithAReadableErrorFrame(): void
78
+ {
79
+ $manager = new ConnectionManager();
80
+ $conn = $this->devConnection('nope');
81
+
82
+ $manager->onOpen($conn);
83
+
84
+ $frame = $conn->lastFrame();
85
+ self::assertIsArray($frame);
86
+ self::assertArrayHasKey('error', $frame);
87
+ self::assertStringContainsString('No socket named `nope`', $frame['error']);
88
+ self::assertSame([1008], $conn->closeCodes);
89
+ }
90
+
91
+ public function testMissingNameIsRefused(): void
92
+ {
93
+ $manager = new ConnectionManager();
94
+ $conn = new FakeConnection('/__pulsepoint/ws', ['Origin' => 'http://localhost:3000']);
95
+
96
+ $manager->onOpen($conn);
97
+
98
+ self::assertStringContainsString('named no socket', $conn->lastFrame()['error']);
99
+ self::assertSame([1008], $conn->closeCodes);
100
+ }
101
+
102
+ public function testArgumentsArriveAsTheFirstFrameAndReachTheHandler(): void
103
+ {
104
+ $received = null;
105
+ SocketRegistry::register('echo', function (Socket $socket, array $args) use (&$received): void {
106
+ $received = $args;
107
+ $socket->onMessage(fn (mixed $value) => $socket->send("you: $value"));
108
+ });
109
+
110
+ $manager = new ConnectionManager();
111
+ $conn = $this->devConnection('echo');
112
+
113
+ $this->openWithArgs($manager, $conn, ['room' => 'lobby']);
114
+ self::assertSame(['room' => 'lobby'], $received);
115
+
116
+ $manager->onMessage($conn, '"hello"');
117
+ self::assertSame('you: hello', $conn->lastFrame());
118
+ self::assertSame([], $conn->closeCodes, 'the conversation stays open');
119
+ }
120
+
121
+ public function testEmptyArgumentsObjectIsAccepted(): void
122
+ {
123
+ $received = null;
124
+ SocketRegistry::register('feed', function (Socket $socket, array $args) use (&$received): void {
125
+ $received = $args;
126
+ });
127
+
128
+ $manager = new ConnectionManager();
129
+ $conn = $this->devConnection('feed');
130
+
131
+ $this->openWithArgs($manager, $conn);
132
+
133
+ self::assertSame([], $received);
134
+ self::assertSame([], $conn->closeCodes);
135
+ }
136
+
137
+ public function testANonObjectFirstFrameIsRefused(): void
138
+ {
139
+ SocketRegistry::register('echo', static function (): void {});
140
+
141
+ $manager = new ConnectionManager();
142
+ $conn = $this->devConnection('echo');
143
+
144
+ $manager->onOpen($conn);
145
+ $manager->onMessage($conn, '[1, 2]');
146
+
147
+ self::assertStringContainsString('not a JSON object', $conn->lastFrame()['error']);
148
+ self::assertSame([1008], $conn->closeCodes);
149
+ }
150
+
151
+ public function testAnUnreadableFrameTravelsBackAsAnErrorFrame(): void
152
+ {
153
+ SocketRegistry::register('echo', function (Socket $socket): void {
154
+ $socket->onMessage(static function (): void {});
155
+ });
156
+
157
+ $manager = new ConnectionManager();
158
+ $conn = $this->devConnection('echo');
159
+
160
+ $this->openWithArgs($manager, $conn);
161
+ $manager->onMessage($conn, '{not json');
162
+
163
+ self::assertStringContainsString('could not read a frame', $conn->lastFrame()['error']);
164
+ }
165
+
166
+ public function testAnOversizedFrameClosesWith1009(): void
167
+ {
168
+ SocketRegistry::register('echo', static function (): void {});
169
+
170
+ $manager = new ConnectionManager();
171
+ $conn = $this->devConnection('echo');
172
+
173
+ $this->openWithArgs($manager, $conn);
174
+ $manager->onMessage($conn, json_encode(str_repeat('x', 5000)));
175
+
176
+ self::assertContains(1009, $conn->closeCodes);
177
+ }
178
+
179
+ public function testProductionRefusesAnUnknownOriginBeforeAnyFrame(): void
180
+ {
181
+ SocketRegistry::register('echo', static function (): void {});
182
+ $this->setEnv('APP_ENV', 'production');
183
+
184
+ $manager = new ConnectionManager();
185
+ $conn = new FakeConnection(
186
+ '/__pulsepoint/ws?name=echo',
187
+ ['Origin' => 'https://evil.example'],
188
+ );
189
+
190
+ $manager->onOpen($conn);
191
+
192
+ self::assertSame([], $conn->sentFrames, 'refused silently, before the wire speaks');
193
+ self::assertSame([1008], $conn->closeCodes);
194
+ }
195
+
196
+ public function testProductionAcceptsAnOriginNamedInTheAllowlist(): void
197
+ {
198
+ SocketRegistry::register('echo', static function (): void {});
199
+ $this->setEnv('APP_ENV', 'production');
200
+ $this->setEnv('WEBSOCKET_ALLOWED_ORIGINS', 'https://app.example');
201
+
202
+ $manager = new ConnectionManager();
203
+ $conn = new FakeConnection(
204
+ '/__pulsepoint/ws?name=echo',
205
+ ['Origin' => 'https://app.example'],
206
+ );
207
+
208
+ $manager->onOpen($conn);
209
+
210
+ self::assertSame([], $conn->closeCodes);
211
+ }
212
+
213
+ public function testRequireAuthRefusesAGuestConnection(): void
214
+ {
215
+ SocketRegistry::register('private', static function (): void {}, requireAuth: true);
216
+
217
+ $manager = new ConnectionManager();
218
+ $conn = $this->devConnection('private');
219
+
220
+ $manager->onOpen($conn);
221
+
222
+ self::assertStringContainsString('signed-in session', $conn->lastFrame()['error']);
223
+ self::assertSame([1008], $conn->closeCodes);
224
+ }
225
+
226
+ public function testAValidAuthCookieReachesTheHandlerAsThePayload(): void
227
+ {
228
+ $seenPayload = false;
229
+ SocketRegistry::register('private', function (Socket $socket) use (&$seenPayload): void {
230
+ $seenPayload = $socket->payload();
231
+ }, requireAuth: true);
232
+
233
+ $jwt = Auth::getInstance()->signIn('admin');
234
+
235
+ $manager = new ConnectionManager();
236
+ $conn = new FakeConnection('/__pulsepoint/ws?name=private', [
237
+ 'Origin' => 'http://localhost:3000',
238
+ 'Cookie' => Auth::$cookieName . '=' . $jwt,
239
+ ]);
240
+
241
+ $this->openWithArgs($manager, $conn);
242
+
243
+ self::assertSame('admin', $seenPayload);
244
+ self::assertSame([], $conn->closeCodes);
245
+ }
246
+
247
+ public function testAHandlerExceptionTravelsAsTheErrorFrame(): void
248
+ {
249
+ SocketRegistry::register('broken', static function (): void {
250
+ throw new \RuntimeException('boom');
251
+ });
252
+
253
+ $manager = new ConnectionManager();
254
+ $conn = $this->devConnection('broken');
255
+
256
+ $this->openWithArgs($manager, $conn);
257
+
258
+ $frame = $conn->lastFrame();
259
+ self::assertArrayHasKey('error', $frame);
260
+ self::assertStringContainsString('boom', $frame['error'], 'development shows the message');
261
+ self::assertSame([1008], $conn->closeCodes);
262
+ }
263
+
264
+ public function testCloseReleasesTheConnectionSlot(): void
265
+ {
266
+ SocketRegistry::register('echo', static function (): void {});
267
+
268
+ $manager = new ConnectionManager();
269
+ $conn = $this->devConnection('echo');
270
+
271
+ $this->openWithArgs($manager, $conn);
272
+ self::assertSame(1, $manager->openConnectionCount());
273
+
274
+ $manager->onClose($conn);
275
+ self::assertSame(0, $manager->openConnectionCount());
276
+ }
277
+ }
@@ -0,0 +1,119 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Tests;
6
+
7
+ use PHPUnit\Framework\TestCase;
8
+ use PP\Security\Csrf;
9
+
10
+ /**
11
+ * The CSRF half of the RPC wire: the `pp_csrf` cookie family and the
12
+ * double-submit validation of the `X-CSRF-Token` header.
13
+ */
14
+ final class CsrfTest extends TestCase
15
+ {
16
+ protected function setUp(): void
17
+ {
18
+ $this->clearCsrfCookies();
19
+ }
20
+
21
+ protected function tearDown(): void
22
+ {
23
+ $this->clearCsrfCookies();
24
+ }
25
+
26
+ private function clearCsrfCookies(): void
27
+ {
28
+ foreach (array_keys($_COOKIE) as $name) {
29
+ if (str_starts_with((string) $name, 'pp_csrf')) {
30
+ unset($_COOKIE[$name]);
31
+ }
32
+ }
33
+ }
34
+
35
+ public function testCookieFamilyUsesThePpCsrfNamesTheRuntimeReads(): void
36
+ {
37
+ $names = Csrf::cookieNames();
38
+
39
+ self::assertNotEmpty($names);
40
+ self::assertSame('pp_csrf', end($names), 'plain pp_csrf is always the fallback name');
41
+
42
+ foreach ($names as $name) {
43
+ self::assertStringStartsWith('pp_csrf', $name);
44
+ }
45
+ }
46
+
47
+ public function testEnsureCookieMintsASignedTokenForEveryCookieName(): void
48
+ {
49
+ Csrf::ensureCookie();
50
+
51
+ foreach (Csrf::cookieNames() as $name) {
52
+ self::assertArrayHasKey($name, $_COOKIE);
53
+ self::assertMatchesRegularExpression(
54
+ '/^[0-9a-f]{32}\.[0-9a-f]{64}$/',
55
+ $_COOKIE[$name],
56
+ 'token is nonce.hmac-sha256'
57
+ );
58
+ }
59
+
60
+ $values = array_map(static fn (string $name) => $_COOKIE[$name], Csrf::cookieNames());
61
+ self::assertCount(1, array_unique($values), 'every issued name carries the same token');
62
+ }
63
+
64
+ public function testEnsureCookieKeepsAnExistingValidToken(): void
65
+ {
66
+ Csrf::ensureCookie();
67
+ $first = $_COOKIE['pp_csrf'];
68
+
69
+ Csrf::ensureCookie();
70
+
71
+ self::assertSame($first, $_COOKIE['pp_csrf']);
72
+ }
73
+
74
+ public function testRotateReplacesTheToken(): void
75
+ {
76
+ Csrf::ensureCookie();
77
+ $first = $_COOKIE['pp_csrf'];
78
+
79
+ Csrf::rotate();
80
+
81
+ self::assertNotSame($first, $_COOKIE['pp_csrf']);
82
+ }
83
+
84
+ public function testValidTokenRoundTrip(): void
85
+ {
86
+ Csrf::ensureCookie();
87
+
88
+ self::assertNull(Csrf::validateHeaderToken($_COOKIE['pp_csrf']));
89
+ }
90
+
91
+ public function testMissingHeaderTokenIsRefused(): void
92
+ {
93
+ Csrf::ensureCookie();
94
+
95
+ self::assertSame('Missing CSRF token', Csrf::validateHeaderToken(''));
96
+ }
97
+
98
+ public function testHeaderThatMatchesNoCookieIsRefused(): void
99
+ {
100
+ Csrf::ensureCookie();
101
+
102
+ self::assertSame(
103
+ 'Invalid CSRF token',
104
+ Csrf::validateHeaderToken(str_repeat('a', 32) . '.' . str_repeat('b', 64))
105
+ );
106
+ }
107
+
108
+ public function testTamperedSignatureIsRefusedEvenWhenTheCookieMatches(): void
109
+ {
110
+ // Double-submit alone is not enough: the signature must verify, so a
111
+ // token an attacker planted (cookie tossing) without the secret fails.
112
+ $forged = str_repeat('a', 32) . '.' . str_repeat('b', 64);
113
+ foreach (Csrf::cookieNames() as $name) {
114
+ $_COOKIE[$name] = $forged;
115
+ }
116
+
117
+ self::assertSame('Invalid CSRF token', Csrf::validateHeaderToken($forged));
118
+ }
119
+ }
@@ -0,0 +1,41 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Tests;
6
+
7
+ use PHPUnit\Framework\TestCase;
8
+ use Tests\Support\Features;
9
+
10
+ /**
11
+ * The suite's own feature awareness: flags come from `prisma-php.json`, the
12
+ * single source of truth for which optional scaffolds exist in this app.
13
+ */
14
+ final class FeaturesTest extends TestCase
15
+ {
16
+ public function testConfigIsReadFromPrismaPhpJson(): void
17
+ {
18
+ $raw = json_decode((string) file_get_contents(DOCUMENT_PATH . '/prisma-php.json'), true);
19
+
20
+ self::assertIsArray($raw);
21
+ self::assertSame($raw, Features::config());
22
+ }
23
+
24
+ public function testEnabledMirrorsTheBooleanFlags(): void
25
+ {
26
+ $config = Features::config();
27
+
28
+ foreach (['websocket', 'mcp', 'swaggerDocs', 'prisma', 'tailwindcss', 'typescript'] as $flag) {
29
+ self::assertSame(
30
+ ($config[$flag] ?? false) === true,
31
+ Features::enabled($flag),
32
+ "Features::enabled('$flag') mirrors prisma-php.json"
33
+ );
34
+ }
35
+ }
36
+
37
+ public function testAnUnknownFlagIsDisabled(): void
38
+ {
39
+ self::assertFalse(Features::enabled('not-a-real-feature'));
40
+ }
41
+ }
@@ -0,0 +1,118 @@
1
+ # App tests
2
+
3
+ PHPUnit tests for the **application** code — `src/Lib/**`, the app's wire
4
+ contracts, and `src/Lib/Websocket/sockets.php` registrations — not the Prisma
5
+ PHP framework under `vendor/tsnc/prisma-php` (that package carries its own
6
+ suite in its source repo).
7
+
8
+ ## The command
9
+
10
+ ```bash
11
+ npm run test
12
+ ```
13
+
14
+ That runs PHPUnit through `settings/run-tests.ts`, which uses the PHP binary
15
+ from `prisma-php.json` (`phpRootPathExe`) so the suite runs with the same PHP
16
+ the app runs on. It exits non-zero on any failure, so CI, a pre-commit hook,
17
+ or an agent knows exactly when something broke.
18
+
19
+ Narrow a run with PHPUnit's own flags after `--`:
20
+
21
+ ```bash
22
+ npm run test -- --filter CsrfTest
23
+ npm run test -- --filter testValidTokenRoundTrip
24
+ ```
25
+
26
+ Running PHPUnit directly also works: `php vendor/phpunit/phpunit/phpunit`
27
+ (configuration is picked up from `phpunit.xml` at the project root).
28
+
29
+ ## Layout
30
+
31
+ One file per surface, flat in `tests/`:
32
+
33
+ | File | Surface |
34
+ | --- | --- |
35
+ | `bootstrap.php` | Shared setup: autoloader, path constants, deterministic test env |
36
+ | `Support/` | Reusable fakes (`Tests\Support\...`, autoloaded via composer `autoload-dev`) |
37
+ | `Support/Features.php` | Feature flags read from `prisma-php.json` |
38
+ | `Support/RequiresFeature.php` | Skip guard for tests of optional features |
39
+ | `FeaturesTest.php` | The suite's own feature awareness mirrors `prisma-php.json` |
40
+ | `CsrfTest.php` | The `pp_csrf` cookie family and `X-CSRF-Token` validation |
41
+ | `RpcWireContractTest.php` | PulsePoint wire headers (`$isRpc` / `$isNavigation` / `$isWire`), including the pin that the legacy header stays dead |
42
+ | `SocketTest.php` | One socket connection: JSON frames, reserved error shape, close semantics |
43
+ | `SocketRegistryTest.php` | Named-socket registration and duplicate refusal |
44
+ | `SocketPoolTest.php` | Broadcast and pruning |
45
+ | `ConnectionManagerTest.php` | The full named-socket wire: handshake refusals, argument frame, auth, limits |
46
+ | `SocketsRegistrationTest.php` | The app's real `sockets.php` loads and names its sockets |
47
+ | `AuthTest.php` | JWT round trip, tamper/expiry/foreign-secret rejection |
48
+
49
+ ## The test environment is deterministic
50
+
51
+ `tests/bootstrap.php` does **not** load the real `.env`. It loads the composer
52
+ autoloader plus `settings/paths.php`, then sets deterministic defaults
53
+ (`APP_ENV=development`, a 32-byte `AUTH_SECRET`, `FUNCTION_CALL_SECRET`) only
54
+ when the variable is not already set — so the suite behaves the same on any
55
+ machine, never depends on real secrets, and CI can still override. The Prisma
56
+ PHP `bootstrap.php` at the project root (route resolution, rendering, CSRF
57
+ cookie issuing) is **not** booted; tests exercise app classes directly.
58
+
59
+ A test that needs a different env value must set it with `putenv(...)` and
60
+ restore the previous value in `tearDown()` (`PP\Env` reads `getenv()` first) —
61
+ see `ConnectionManagerTest::setEnv()` for the pattern.
62
+
63
+ ## Optional features are optional in the suite too
64
+
65
+ `prisma-php.json` decides which optional scaffolds exist (`websocket`, `mcp`,
66
+ `swaggerDocs`, `prisma`, ...). A test for an optional feature must guard on
67
+ the flag, or a freshly generated app without that feature fatals instead of
68
+ passing:
69
+
70
+ ```php
71
+ use Tests\Support\RequiresFeature;
72
+
73
+ final class MySocketTest extends TestCase
74
+ {
75
+ use RequiresFeature;
76
+
77
+ protected function setUp(): void
78
+ {
79
+ $this->requireFeature('websocket'); // FIRST line, before touching the scaffold
80
+ }
81
+ }
82
+ ```
83
+
84
+ When the flag is off the test reports itself as skipped with the reason and
85
+ the enable command — never as a failure. `tearDown()` still runs after a
86
+ skip, so teardown code must not assume the feature's classes exist (see
87
+ `SocketRegistryTest::resetRegistry()` for the `class_exists` guard).
88
+
89
+ The websocket suites (`SocketTest`, `SocketRegistryTest`, `SocketPoolTest`,
90
+ `ConnectionManagerTest`, `SocketsRegistrationTest`) are gated this way. Core
91
+ surfaces — CSRF, the RPC wire headers, auth — are always-on and never gated.
92
+
93
+ ## Conventions
94
+
95
+ - one `SomethingTest.php` per surface, in the `Tests` namespace; shared fakes
96
+ live in `tests/Support` under `Tests\Support`
97
+ - static registries (`SocketRegistry`) are reset in `setUp()`/`tearDown()` via
98
+ reflection so tests never depend on each other's order
99
+ - superglobals a test touches (`$_SERVER`, `$_COOKIE`, env) are backed up in
100
+ `setUp()` and restored in `tearDown()`
101
+ - wire tests assert **frames and close codes**, not internals:
102
+ `Tests\Support\FakeConnection` records everything the server sends, so a
103
+ test reads the conversation the browser would have seen
104
+ - expected framework failures are part of the contract — a refusal test
105
+ asserts the readable `{"error": "..."}` frame (or HTTP status), not just
106
+ "something failed"
107
+ - tests cover app-level code and the wire contracts the app depends on; do
108
+ not test Prisma PHP internals from here — those belong to the package
109
+ repo's own suite (`vendor/tsnc/prisma-php/tests` when vendored)
110
+
111
+ ## For AI agents
112
+
113
+ Read this file plus `node_modules/prisma-php/dist/docs/testing.md` before
114
+ adding or changing tests. When you add an app feature with logic worth
115
+ protecting — an exposed function's validation, a socket handler, an auth
116
+ rule — add the matching `*Test.php` here in the same change, and run
117
+ `npm run test` before declaring the work done. A red suite is a finding to
118
+ fix, not to silence.
@@ -0,0 +1,101 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Tests;
6
+
7
+ use PHPUnit\Framework\TestCase;
8
+ use PP\Request;
9
+
10
+ /**
11
+ * The PulsePoint wire headers, as the app depends on them.
12
+ *
13
+ * `pp.rpc(...)` posts with `X-PP-RPC: true` + `X-PP-Function`; SPA navigation
14
+ * fetches with `X-PP-Navigation: true`; both carry `X-PulsePoint-Wire: true`.
15
+ * This is a complete migration — the legacy `PP-Wire-Request` header must NOT
16
+ * be recognized any more, and that is pinned here on purpose.
17
+ */
18
+ final class RpcWireContractTest extends TestCase
19
+ {
20
+ private array $serverBackup = [];
21
+
22
+ protected function setUp(): void
23
+ {
24
+ $this->serverBackup = $_SERVER;
25
+ }
26
+
27
+ protected function tearDown(): void
28
+ {
29
+ $_SERVER = $this->serverBackup;
30
+ }
31
+
32
+ private function initRequest(array $server): void
33
+ {
34
+ $_SERVER = array_merge([
35
+ 'HTTP_HOST' => 'example.test',
36
+ 'SCRIPT_NAME' => '/index.php',
37
+ 'REMOTE_ADDR' => '127.0.0.1',
38
+ ], $server);
39
+
40
+ Request::init();
41
+ }
42
+
43
+ public function testRpcPostIsDetected(): void
44
+ {
45
+ $this->initRequest([
46
+ 'REQUEST_METHOD' => 'POST',
47
+ 'CONTENT_TYPE' => 'application/json',
48
+ 'HTTP_X_PULSEPOINT_WIRE' => 'true',
49
+ 'HTTP_X_PP_RPC' => 'true',
50
+ 'HTTP_X_PP_FUNCTION' => 'saveProfile',
51
+ ]);
52
+
53
+ self::assertTrue(Request::$isWire);
54
+ self::assertTrue(Request::$isRpc);
55
+ self::assertFalse(Request::$isNavigation);
56
+ }
57
+
58
+ public function testNavigationGetIsWireButNotRpc(): void
59
+ {
60
+ $this->initRequest([
61
+ 'REQUEST_METHOD' => 'GET',
62
+ 'HTTP_X_PULSEPOINT_WIRE' => 'true',
63
+ 'HTTP_X_PP_NAVIGATION' => 'true',
64
+ ]);
65
+
66
+ self::assertTrue(Request::$isWire);
67
+ self::assertFalse(Request::$isRpc);
68
+ self::assertTrue(Request::$isNavigation);
69
+ }
70
+
71
+ public function testRpcHeaderOnAGetIsNotAnRpc(): void
72
+ {
73
+ $this->initRequest([
74
+ 'REQUEST_METHOD' => 'GET',
75
+ 'HTTP_X_PP_RPC' => 'true',
76
+ ]);
77
+
78
+ self::assertFalse(Request::$isRpc);
79
+ }
80
+
81
+ public function testPlainRequestCarriesNoWireFlags(): void
82
+ {
83
+ $this->initRequest(['REQUEST_METHOD' => 'GET']);
84
+
85
+ self::assertFalse(Request::$isWire);
86
+ self::assertFalse(Request::$isRpc);
87
+ self::assertFalse(Request::$isNavigation);
88
+ }
89
+
90
+ public function testLegacyWireHeaderIsNoLongerRecognized(): void
91
+ {
92
+ $this->initRequest([
93
+ 'REQUEST_METHOD' => 'POST',
94
+ 'CONTENT_TYPE' => 'application/json',
95
+ 'HTTP_PP_WIRE_REQUEST' => 'true',
96
+ ]);
97
+
98
+ self::assertFalse(Request::$isWire, 'complete migration: no legacy header support');
99
+ self::assertFalse(Request::$isRpc);
100
+ }
101
+ }