Skip to content

Commit 78de063

Browse files
authored
Allow the user ID to be customized (#261)
1 parent 3bb74b3 commit 78de063

File tree

4 files changed

+184
-19
lines changed

4 files changed

+184
-19
lines changed

src/UserProvider.php

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ final class UserProvider
2121
*/
2222
public $userDetailsResolverResolver;
2323

24+
/**
25+
* @var array{id: mixed, name?: mixed, username?: mixed}
26+
*/
27+
private ?array $resolvedDetails;
28+
2429
/**
2530
* @var (callable(callable(AuthManager): mixed): mixed)
2631
*/
@@ -54,11 +59,11 @@ public function id(): LazyValue|string
5459
}
5560

5661
if ($auth->hasUser()) {
57-
return $this->currentUserId($auth);
62+
return $this->userId($auth->user()); // @phpstan-ignore argument.type
5863
}
5964

6065
if ($this->rememberedUser) {
61-
return $this->rememberedUserId();
66+
return $this->userId($this->rememberedUser);
6267
}
6368

6469
return $this->lazyUserId();
@@ -83,32 +88,21 @@ public function resolvedUserId(): string
8388
}
8489

8590
if ($auth->hasUser()) {
86-
return $this->currentUserId($auth);
91+
return $this->userId($auth->user()); // @phpstan-ignore argument.type
8792
}
8893

8994
if ($this->rememberedUser) {
90-
return $this->rememberedUserId();
95+
return $this->userId($this->rememberedUser);
9196
}
9297

9398
return '';
9499
});
95100
}
96101

97-
private function currentUserId(AuthManager $auth): string
98-
{
99-
try {
100-
return Str::tinyText((string) $auth->id());
101-
} catch (Throwable $e) {
102-
$this->reportResolvingUserIdException($e);
103-
104-
return '';
105-
}
106-
}
107-
108-
private function rememberedUserId(): string
102+
private function userId(Authenticatable $user): string
109103
{
110104
try {
111-
return Str::tinyText((string) $this->rememberedUser?->getAuthIdentifier()); // @phpstan-ignore cast.string
105+
return Str::tinyText((string) ($this->resolvedDetails($user)['id'] ?? '')); // @phpstan-ignore cast.string
112106
} catch (Throwable $e) {
113107
$this->reportResolvingUserIdException($e);
114108

@@ -125,10 +119,22 @@ public function details(): ?array
125119
? $auth->user() ?? $this->rememberedUser
126120
: $this->rememberedUser);
127121

122+
return $this->resolvedDetails($user);
123+
}
124+
125+
/**
126+
* @return array{ id: mixed, name?: mixed, username?: mixed }|null
127+
*/
128+
private function resolvedDetails(?Authenticatable $user): ?array
129+
{
128130
if ($user === null) {
129131
return null;
130132
}
131133

134+
if (isset($this->resolvedDetails)) {
135+
return $this->resolvedDetails;
136+
}
137+
132138
try {
133139
$id = $user->getAuthIdentifier();
134140
} catch (Throwable $e) {
@@ -140,14 +146,14 @@ public function details(): ?array
140146
$resolver = call_user_func($this->userDetailsResolverResolver);
141147

142148
if ($resolver === null) {
143-
return [
149+
return $this->resolvedDetails = [
144150
'id' => $id,
145151
'name' => $user->name ?? '',
146152
'username' => $user->email ?? '',
147153
];
148154
}
149155

150-
return [
156+
return $this->resolvedDetails = [
151157
'id' => $id,
152158
...$resolver($user),
153159
];
@@ -161,6 +167,7 @@ public function remember(Authenticatable $user): void
161167
public function flush(): void
162168
{
163169
$this->rememberedUser = null;
170+
$this->resolvedDetails = null;
164171
$this->alreadyReportedResolvingUserIdException = false;
165172
}
166173

tests/Feature/Sensors/UserSensorTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public function test_it_can_customize_the_capture_of_user_details(): void
112112
'name' => 'Tim',
113113
'username' => 'timacdonald',
114114
]]);
115+
$ingest->assertLatestWrite('request:0.user', '123');
115116
}
116117

117118
public function test_it_handles_authenticatable_objects_without_name_or_email_properties(): void
@@ -173,6 +174,7 @@ public function getRememberTokenName()
173174
'name' => '',
174175
'username' => '',
175176
]]);
177+
$ingest->assertLatestWrite('request:0.user', '123');
176178
}
177179

178180
public function test_it_can_only_collect_the_user_id(): void
@@ -200,6 +202,7 @@ public function test_it_can_only_collect_the_user_id(): void
200202
'name' => '',
201203
'username' => '',
202204
]]);
205+
$ingest->assertLatestWrite('request:0.user', '123');
203206
}
204207

205208
public function test_it_it_captures_the_user_id_even_when_excluded_from_the_nightwatch_user_return_array(): void
@@ -225,6 +228,7 @@ public function test_it_it_captures_the_user_id_even_when_excluded_from_the_nigh
225228
'name' => '',
226229
'username' => '',
227230
]]);
231+
$ingest->assertLatestWrite('request:0.user', '567');
228232
}
229233

230234
public function test_it_gracefully_handles_exceptions_while_resolving_user_ids(): void

tests/Unit/OctaneTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,16 @@ public function test_it_prepares_for_next_request(): void
6464
$this->assertSame('Whoops!', $this->core->executionState->exceptionPreview);
6565
$this->assertSame('GET /test', $this->core->executionState->executionPreview);
6666
$this->assertSame(ExecutionStage::End, $this->core->executionState->stage);
67+
$this->assertSame('5', $this->core->executionState->user->id());
6768

6869
$this->core->uuid->uuidResolver = fn () => '8B4F773A-81AB-4273-97D5-C7BECBC173BE';
6970
$this->core->clock->microtimeResolver = fn () => 56789;
7071
$this->core->prepareForNextRequest();
7172

73+
$this->actingAs(new GenericUser([
74+
'id' => 6,
75+
]));
76+
7277
$this->assertSame('8B4F773A-81AB-4273-97D5-C7BECBC173BE', $this->core->executionState->id()->jsonSerialize());
7378
$this->assertSame('8B4F773A-81AB-4273-97D5-C7BECBC173BE', $this->core->executionState->trace);
7479
$this->assertSame('8B4F773A-81AB-4273-97D5-C7BECBC173BE', Compatibility::getTraceIdFromContext());
@@ -87,5 +92,6 @@ public function test_it_prepares_for_next_request(): void
8792
$this->assertSame(56789.0, $this->core->executionState->timestamp);
8893
$this->assertSame(56789.0, $this->core->executionState->currentExecutionStageStartedAtMicrotime);
8994
$this->assertSame(ExecutionStage::BeforeMiddleware, $this->core->executionState->stage);
95+
$this->assertSame('6', $this->core->executionState->user->id());
9096
}
9197
}

tests/Unit/UserProviderTest.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
use App\Models\User;
66
use Illuminate\Auth\GenericUser;
7+
use Illuminate\Contracts\Auth\Authenticatable;
78
use Illuminate\Support\Facades\Auth;
89
use Illuminate\Support\Facades\DB;
10+
use Illuminate\Support\Facades\Route;
11+
use Laravel\Nightwatch\Facades\Nightwatch;
912
use RuntimeException;
1013
use Tests\TestCase;
1114

@@ -156,4 +159,149 @@ public function getAuthIdentifier()
156159
$ingest->assertLatestWrite('query:0.sql', 'select * from users');
157160
$ingest->assertLatestWrite('query:1.sql', 'select * from users');
158161
}
162+
163+
public function test_the_user_id_can_be_customized(): void
164+
{
165+
$ingest = $this->fakeIngest();
166+
Route::get('/users', fn () => User::all());
167+
$user = User::make([
168+
'id' => '456',
169+
'name' => 'Tim MacDonald',
170+
'email' => '[email protected]',
171+
]);
172+
Nightwatch::user(fn (Authenticatable $user) => [
173+
'id' => '123-'.$user->getAuthIdentifier(),
174+
'name' => $user->name,
175+
'username' => $user->email,
176+
]);
177+
178+
$response = $this->actingAs($user)->get('/users');
179+
180+
$response->assertOk();
181+
$ingest->assertWrittenTimes(1);
182+
$ingest->assertLatestWrite('user:0.id', '123-456');
183+
$ingest->assertLatestWrite('query:0.user', '123-456');
184+
$ingest->assertLatestWrite('request:0.user', '123-456');
185+
}
186+
187+
public function test_it_allows_the_id_to_be_omitted_when_customizing(): void
188+
{
189+
$ingest = $this->fakeIngest();
190+
Route::get('/users', fn () => User::all());
191+
$user = User::make([
192+
'id' => '456',
193+
'name' => 'Tim MacDonald',
194+
'email' => '[email protected]',
195+
]);
196+
Nightwatch::user(fn (Authenticatable $user) => [
197+
'name' => $user->name,
198+
'username' => $user->email,
199+
]);
200+
201+
$response = $this->actingAs($user)->get('/users');
202+
203+
$response->assertOk();
204+
$ingest->assertWrittenTimes(1);
205+
$ingest->assertLatestWrite('user:0.id', '456');
206+
$ingest->assertLatestWrite('query:0.user', '456');
207+
$ingest->assertLatestWrite('request:0.user', '456');
208+
}
209+
210+
public function test_the_user_id_can_be_customized_when_the_user_logs_out(): void
211+
{
212+
$ingest = $this->fakeIngest();
213+
Route::post('/logout', function () {
214+
Auth::logout();
215+
User::all();
216+
});
217+
$user = User::make([
218+
'id' => '456',
219+
'name' => 'Tim MacDonald',
220+
'email' => '[email protected]',
221+
]);
222+
Nightwatch::user(fn (Authenticatable $user) => [
223+
'id' => '123-'.$user->getAuthIdentifier(),
224+
'name' => $user->name,
225+
'username' => $user->email,
226+
]);
227+
228+
$response = $this->actingAs($user)->post('/logout');
229+
230+
$response->assertOk();
231+
$ingest->assertWrittenTimes(1);
232+
$ingest->assertLatestWrite('user:0.id', '123-456');
233+
$ingest->assertLatestWrite('query:0.user', '123-456');
234+
$ingest->assertLatestWrite('request:0.user', '123-456');
235+
}
236+
237+
public function test_the_id_can_be_omitted_when_customizing_and_the_user_logs_out(): void
238+
{
239+
$ingest = $this->fakeIngest();
240+
Route::post('/logout', function () {
241+
Auth::logout();
242+
User::all();
243+
});
244+
$user = User::make([
245+
'id' => '456',
246+
'name' => 'Tim MacDonald',
247+
'email' => '[email protected]',
248+
]);
249+
Nightwatch::user(fn (Authenticatable $user) => [
250+
'name' => $user->name,
251+
'username' => $user->email,
252+
]);
253+
254+
$response = $this->actingAs($user)->post('/logout');
255+
256+
$response->assertOk();
257+
$ingest->assertWrittenTimes(1);
258+
$ingest->assertLatestWrite('user:0.id', '456');
259+
$ingest->assertLatestWrite('query:0.user', '456');
260+
$ingest->assertLatestWrite('request:0.user', '456');
261+
}
262+
263+
public function test_it_doesnt_call_the_resolver_multiple_times(): void
264+
{
265+
$this->fakeIngest();
266+
Route::get('/users', fn () => User::all());
267+
$user = User::make();
268+
$calls = 0;
269+
Nightwatch::user(function (Authenticatable $user) use (&$calls) {
270+
$calls++;
271+
272+
return [
273+
'name' => $user->name,
274+
'username' => $user->email,
275+
];
276+
});
277+
278+
$response = $this->actingAs($user)->get('/users');
279+
280+
$response->assertOk();
281+
$this->assertSame(1, $calls);
282+
}
283+
284+
public function test_it_doesnt_call_the_resolver_multiple_times_when_logging_out(): void
285+
{
286+
$this->fakeIngest();
287+
Route::post('/logout', function () {
288+
Auth::logout();
289+
User::all();
290+
});
291+
$user = User::make();
292+
$calls = 0;
293+
Nightwatch::user(function (Authenticatable $user) use (&$calls) {
294+
$calls++;
295+
296+
return [
297+
'name' => $user->name,
298+
'username' => $user->email,
299+
];
300+
});
301+
302+
$response = $this->actingAs($user)->post('/logout');
303+
304+
$response->assertOk();
305+
$this->assertSame(1, $calls);
306+
}
159307
}

0 commit comments

Comments
 (0)