feat(auth): implement admin app password reset attempt functionality
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Auth\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||||
|
use App\Domains\Auth\Requests\AdminAppCreateResetPasswordAttemptRequest;
|
||||||
|
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class CreateAdminAppResetPasswordAttemptController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ResetPasswordAttemptService $resetPasswordAttemptService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function __invoke(AdminAppCreateResetPasswordAttemptRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->resetPasswordAttemptService->createForAdminAppEmail(
|
||||||
|
$request->validated('email'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 'auth.password_reset_requested',
|
||||||
|
'message' => __('api.auth.password_reset_requested'),
|
||||||
|
'status' => ResetPasswordAttempt::STATUS_PENDING,
|
||||||
|
], 202);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Auth\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class AdminAppCreateResetPasswordAttemptRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$email = $this->input('email');
|
||||||
|
|
||||||
|
if (is_string($email)) {
|
||||||
|
$this->merge(['email' => Str::lower(trim($email))]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, mixed> */
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -243,7 +243,18 @@ class PasswordLoginService
|
|||||||
|
|
||||||
if ($attempts >= $maxAttempts && $previousAttempts < $maxAttempts) {
|
if ($attempts >= $maxAttempts && $previousAttempts < $maxAttempts) {
|
||||||
try {
|
try {
|
||||||
$this->resetPasswordAttemptService->createForEmail($user->email, $tenantCode, 'account_locked');
|
if ($user->rol_codigo === RoleCode::AdminApp->value) {
|
||||||
|
$this->resetPasswordAttemptService->createForAdminAppEmail(
|
||||||
|
$user->email,
|
||||||
|
'account_locked',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->resetPasswordAttemptService->createForEmail(
|
||||||
|
$user->email,
|
||||||
|
$tenantCode,
|
||||||
|
'account_locked',
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::error('Failed to trigger reset password on account lock', [
|
Log::error('Failed to trigger reset password on account lock', [
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Auth\Services;
|
|||||||
|
|
||||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||||
use App\Domains\Auth\Models\User;
|
use App\Domains\Auth\Models\User;
|
||||||
|
use App\Domains\Authorization\Enums\RoleCode;
|
||||||
use App\Domains\Notification\Events\PasswordResetRequested;
|
use App\Domains\Notification\Events\PasswordResetRequested;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -22,28 +23,12 @@ class ResetPasswordAttemptService
|
|||||||
->lockForUpdate()
|
->lockForUpdate()
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($user === null) {
|
return $this->createAttemptForUser(
|
||||||
Log::warning('Password reset attempt was not created because the user was not found.', [
|
$user,
|
||||||
'email_fingerprint' => $emailFingerprint,
|
$reason,
|
||||||
]);
|
$emailFingerprint,
|
||||||
|
'Password reset attempt was not created because the user was not found.',
|
||||||
return null;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
$user->resetPasswordAttempts()
|
|
||||||
->whereIn('status', [
|
|
||||||
ResetPasswordAttempt::STATUS_PENDING,
|
|
||||||
ResetPasswordAttempt::STATUS_VALIDATED,
|
|
||||||
])
|
|
||||||
->update(['status' => ResetPasswordAttempt::STATUS_EXPIRED]);
|
|
||||||
|
|
||||||
$attempt = $user->resetPasswordAttempts()->create([
|
|
||||||
'codigo' => $this->generateCode(),
|
|
||||||
'reason' => $reason,
|
|
||||||
'status' => ResetPasswordAttempt::STATUS_PENDING,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $attempt->getKey();
|
|
||||||
});
|
});
|
||||||
} catch (Throwable $exception) {
|
} catch (Throwable $exception) {
|
||||||
Log::error('Failed to create password reset attempt.', [
|
Log::error('Failed to create password reset attempt.', [
|
||||||
@@ -54,20 +39,58 @@ class ResetPasswordAttemptService
|
|||||||
throw $exception;
|
throw $exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($attemptId !== null) {
|
$this->dispatchPasswordResetRequested(
|
||||||
try {
|
$attemptId,
|
||||||
PasswordResetRequested::dispatch($attemptId, $tenantCode);
|
$tenantCode,
|
||||||
} catch (Throwable $exception) {
|
PasswordResetRequested::CHANNEL_STOREFRONT,
|
||||||
Log::error('Failed to dispatch password reset email.', [
|
$emailFingerprint,
|
||||||
'attempt_id' => $attemptId,
|
);
|
||||||
'tenant_code' => $tenantCode,
|
}
|
||||||
'email_fingerprint' => $emailFingerprint,
|
|
||||||
'exception' => $exception,
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw $exception;
|
public function createForAdminAppEmail(string $email, string $reason = 'manual'): void
|
||||||
}
|
{
|
||||||
|
$emailFingerprint = $this->emailFingerprint($email);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array {
|
||||||
|
$user = User::query()
|
||||||
|
->where('email', $email)
|
||||||
|
->where('rol_codigo', RoleCode::AdminApp->value)
|
||||||
|
->whereNotNull('tenant_codigo')
|
||||||
|
->lockForUpdate()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$attemptId = $this->createAttemptForUser(
|
||||||
|
$user,
|
||||||
|
$reason,
|
||||||
|
$emailFingerprint,
|
||||||
|
'AdminApp password reset attempt was not created because the user was not found.',
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($user === null || $attemptId === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'attempt_id' => $attemptId,
|
||||||
|
'tenant_code' => $user->tenant_codigo,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
Log::error('Failed to create AdminApp password reset attempt.', [
|
||||||
|
'email_fingerprint' => $emailFingerprint,
|
||||||
|
'exception' => $exception,
|
||||||
|
]);
|
||||||
|
|
||||||
|
throw $exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->dispatchPasswordResetRequested(
|
||||||
|
$result['attempt_id'] ?? null,
|
||||||
|
$result['tenant_code'] ?? null,
|
||||||
|
PasswordResetRequested::CHANNEL_ADMINAPP,
|
||||||
|
$emailFingerprint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateCode(string $email, string $code): bool
|
public function validateCode(string $email, string $code): bool
|
||||||
@@ -169,6 +192,61 @@ class ResetPasswordAttemptService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function createAttemptForUser(
|
||||||
|
?User $user,
|
||||||
|
string $reason,
|
||||||
|
string $emailFingerprint,
|
||||||
|
string $userNotFoundMessage,
|
||||||
|
): ?int {
|
||||||
|
if ($user === null) {
|
||||||
|
Log::warning($userNotFoundMessage, [
|
||||||
|
'email_fingerprint' => $emailFingerprint,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->resetPasswordAttempts()
|
||||||
|
->whereIn('status', [
|
||||||
|
ResetPasswordAttempt::STATUS_PENDING,
|
||||||
|
ResetPasswordAttempt::STATUS_VALIDATED,
|
||||||
|
])
|
||||||
|
->update(['status' => ResetPasswordAttempt::STATUS_EXPIRED]);
|
||||||
|
|
||||||
|
$attempt = $user->resetPasswordAttempts()->create([
|
||||||
|
'codigo' => $this->generateCode(),
|
||||||
|
'reason' => $reason,
|
||||||
|
'status' => ResetPasswordAttempt::STATUS_PENDING,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $attempt->getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function dispatchPasswordResetRequested(
|
||||||
|
?int $attemptId,
|
||||||
|
?string $tenantCode,
|
||||||
|
string $channel,
|
||||||
|
string $emailFingerprint,
|
||||||
|
): void {
|
||||||
|
if ($attemptId === null || $tenantCode === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PasswordResetRequested::dispatch($attemptId, $tenantCode, $channel);
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
Log::error('Failed to dispatch password reset email.', [
|
||||||
|
'attempt_id' => $attemptId,
|
||||||
|
'tenant_code' => $tenantCode,
|
||||||
|
'channel' => $channel,
|
||||||
|
'email_fingerprint' => $emailFingerprint,
|
||||||
|
'exception' => $exception,
|
||||||
|
]);
|
||||||
|
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function generateCode(): string
|
private function generateCode(): string
|
||||||
{
|
{
|
||||||
return str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
return str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||||
|
|||||||
@@ -2,10 +2,19 @@
|
|||||||
|
|
||||||
use App\Domains\Auth\Controllers\AdminAppLoginController;
|
use App\Domains\Auth\Controllers\AdminAppLoginController;
|
||||||
use App\Domains\Auth\Controllers\AdminAppMeController;
|
use App\Domains\Auth\Controllers\AdminAppMeController;
|
||||||
|
use App\Domains\Auth\Controllers\CreateAdminAppResetPasswordAttemptController;
|
||||||
|
use App\Domains\Auth\Controllers\ResetPasswordController;
|
||||||
|
use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::prefix('v1/adminapp')->group(function (): void {
|
Route::prefix('v1/adminapp')->group(function (): void {
|
||||||
Route::post('login', AdminAppLoginController::class)->middleware('throttle:login');
|
Route::post('login', AdminAppLoginController::class)->middleware('throttle:login');
|
||||||
|
Route::post('password/reset-attempts', CreateAdminAppResetPasswordAttemptController::class)
|
||||||
|
->middleware('throttle:5,1');
|
||||||
|
Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class)
|
||||||
|
->middleware('throttle:10,1');
|
||||||
|
Route::post('password/reset', ResetPasswordController::class)
|
||||||
|
->middleware('throttle:5,1');
|
||||||
Route::middleware(['auth:sanctum', 'adminapp.tenant'])
|
Route::middleware(['auth:sanctum', 'adminapp.tenant'])
|
||||||
->get('me', AdminAppMeController::class);
|
->get('me', AdminAppMeController::class);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ class PasswordResetRequested
|
|||||||
{
|
{
|
||||||
use Dispatchable, SerializesModels;
|
use Dispatchable, SerializesModels;
|
||||||
|
|
||||||
|
public const CHANNEL_STOREFRONT = 'storefront';
|
||||||
|
|
||||||
|
public const CHANNEL_ADMINAPP = 'adminapp';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly int $attemptId,
|
public readonly int $attemptId,
|
||||||
public readonly string $tenantCode,
|
public readonly string $tenantCode,
|
||||||
|
public readonly string $channel = self::CHANNEL_STOREFRONT,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ class SendPasswordResetEmail implements ShouldQueueAfterCommit
|
|||||||
app(NotificationMailService::class)->sendPasswordResetCode(
|
app(NotificationMailService::class)->sendPasswordResetCode(
|
||||||
$event->attemptId,
|
$event->attemptId,
|
||||||
$event->tenantCode,
|
$event->tenantCode,
|
||||||
|
$event->channel,
|
||||||
);
|
);
|
||||||
} catch (Throwable $exception) {
|
} catch (Throwable $exception) {
|
||||||
Log::error('Failed to send password reset email.', [
|
Log::error('Failed to send password reset email.', [
|
||||||
'attempt_id' => $event->attemptId,
|
'attempt_id' => $event->attemptId,
|
||||||
'tenant_code' => $event->tenantCode,
|
'tenant_code' => $event->tenantCode,
|
||||||
|
'channel' => $event->channel,
|
||||||
'exception' => $exception,
|
'exception' => $exception,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Notification\Services;
|
|||||||
|
|
||||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||||
use App\Domains\Auth\Models\User;
|
use App\Domains\Auth\Models\User;
|
||||||
|
use App\Domains\Notification\Events\PasswordResetRequested;
|
||||||
use App\Domains\Integration\Services\MailService;
|
use App\Domains\Integration\Services\MailService;
|
||||||
use App\Domains\Purchase\Models\Purchase;
|
use App\Domains\Purchase\Models\Purchase;
|
||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
@@ -31,9 +32,16 @@ class NotificationMailService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendPasswordResetCode(int $attemptId, string $tenantCode): void
|
public function sendPasswordResetCode(
|
||||||
|
int $attemptId,
|
||||||
|
string $tenantCode,
|
||||||
|
string $channel = PasswordResetRequested::CHANNEL_STOREFRONT,
|
||||||
|
): void
|
||||||
{
|
{
|
||||||
$tenant = Tenant::query()->where('codigo', $tenantCode)->firstOrFail();
|
$tenant = Tenant::query()
|
||||||
|
->with('websiteType')
|
||||||
|
->where('codigo', $tenantCode)
|
||||||
|
->firstOrFail();
|
||||||
$attempt = ResetPasswordAttempt::query()
|
$attempt = ResetPasswordAttempt::query()
|
||||||
->with('user')
|
->with('user')
|
||||||
->findOrFail($attemptId);
|
->findOrFail($attemptId);
|
||||||
@@ -48,12 +56,19 @@ class NotificationMailService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$recoveryDomain = $channel === PasswordResetRequested::CHANNEL_ADMINAPP
|
||||||
|
? $tenant->websiteType?->dominio
|
||||||
|
: $tenant->dominio;
|
||||||
|
$recoveryUrl = $recoveryDomain === null
|
||||||
|
? null
|
||||||
|
: 'https://'.$recoveryDomain.'/recuperar-contrasena/codigo?email='.urlencode($attempt->user->email);
|
||||||
|
|
||||||
$this->mailService
|
$this->mailService
|
||||||
->forTenant($tenantCode)
|
->forTenant($tenantCode)
|
||||||
->send(
|
->send(
|
||||||
$attempt->user->email,
|
$attempt->user->email,
|
||||||
"Código para recuperar tu contraseña - {$tenant->nombre}",
|
"Código para recuperar tu contraseña - {$tenant->nombre}",
|
||||||
view('mail.notifications.password-reset', compact('tenant', 'attempt'))->render(),
|
view('mail.notifications.password-reset', compact('tenant', 'attempt', 'recoveryUrl'))->render(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@php
|
@if($recoveryUrl)
|
||||||
$recoveryUrl = 'https://' . $tenant->dominio . '/recuperar-contrasena/codigo?email=' . urlencode($attempt->user->email);
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 28px;">
|
<div style="text-align: center; margin-bottom: 28px;">
|
||||||
<a href="{{ $recoveryUrl }}"
|
<a href="{{ $recoveryUrl }}"
|
||||||
style="display: inline-block; padding: 12px 24px; background-color: {{ $tenant->primary_color }}; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: bold;">
|
style="display: inline-block; padding: 12px 24px; background-color: {{ $tenant->primary_color }}; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: bold;">
|
||||||
Ingresar código ahora
|
Ingresar código ahora
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<p style="color: #64748b; font-size: 14px;">
|
<p style="color: #64748b; font-size: 14px;">
|
||||||
@if($attempt->reason === 'account_locked')
|
@if($attempt->reason === 'account_locked')
|
||||||
|
|||||||
61
tests/Feature/Auth/AdminAppResetPasswordControllerTest.php
Normal file
61
tests/Feature/Auth/AdminAppResetPasswordControllerTest.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||||
|
use App\Domains\Auth\Models\User;
|
||||||
|
use App\Domains\Authorization\Enums\RoleCode;
|
||||||
|
use App\Domains\Authorization\Models\Role;
|
||||||
|
use App\Domains\Notification\Events\PasswordResetRequested;
|
||||||
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminAppResetPasswordControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_it_creates_an_attempt_for_a_tenant_bound_adminapp_user(): void
|
||||||
|
{
|
||||||
|
Event::fake([PasswordResetRequested::class]);
|
||||||
|
Role::query()->create([
|
||||||
|
'codigo' => RoleCode::AdminApp->value,
|
||||||
|
'nombre' => 'AdminApp',
|
||||||
|
]);
|
||||||
|
$tenant = Tenant::query()->create([
|
||||||
|
'codigo' => 'acme',
|
||||||
|
'nombre' => 'Acme',
|
||||||
|
'dominio' => 'store.acme.test',
|
||||||
|
]);
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'email' => 'admin@example.com',
|
||||||
|
'rol_codigo' => RoleCode::AdminApp->value,
|
||||||
|
'tenant_codigo' => $tenant->codigo,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->postJson('/api/v1/adminapp/password/reset-attempts', [
|
||||||
|
'email' => ' ADMIN@EXAMPLE.COM ',
|
||||||
|
])->assertAccepted()->assertJsonPath('status', ResetPasswordAttempt::STATUS_PENDING);
|
||||||
|
|
||||||
|
$attempt = $user->resetPasswordAttempts()->sole();
|
||||||
|
Event::assertDispatched(
|
||||||
|
PasswordResetRequested::class,
|
||||||
|
fn (PasswordResetRequested $event): bool => $event->attemptId === $attempt->id
|
||||||
|
&& $event->tenantCode === $tenant->codigo
|
||||||
|
&& $event->channel === PasswordResetRequested::CHANNEL_ADMINAPP,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_does_not_create_an_attempt_for_a_non_adminapp_user(): void
|
||||||
|
{
|
||||||
|
Event::fake([PasswordResetRequested::class]);
|
||||||
|
|
||||||
|
$this->postJson('/api/v1/adminapp/password/reset-attempts', [
|
||||||
|
'email' => User::factory()->create()->email,
|
||||||
|
])->assertAccepted()->assertJsonPath('status', ResetPasswordAttempt::STATUS_PENDING);
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('reset_password_attempts', 0);
|
||||||
|
Event::assertNotDispatched(PasswordResetRequested::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ class SendPasswordResetEmailTest extends TestCase
|
|||||||
$mailService = \Mockery::mock(NotificationMailService::class);
|
$mailService = \Mockery::mock(NotificationMailService::class);
|
||||||
$mailService->shouldReceive('sendPasswordResetCode')
|
$mailService->shouldReceive('sendPasswordResetCode')
|
||||||
->once()
|
->once()
|
||||||
->with(10, 'tenant-test')
|
->with(10, 'tenant-test', PasswordResetRequested::CHANNEL_STOREFRONT)
|
||||||
->andThrow($exception);
|
->andThrow($exception);
|
||||||
$this->app->instance(NotificationMailService::class, $mailService);
|
$this->app->instance(NotificationMailService::class, $mailService);
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ class SendPasswordResetEmailTest extends TestCase
|
|||||||
'Failed to send password reset email.',
|
'Failed to send password reset email.',
|
||||||
\Mockery::on(fn (array $context): bool => $context['attempt_id'] === 10
|
\Mockery::on(fn (array $context): bool => $context['attempt_id'] === 10
|
||||||
&& $context['tenant_code'] === 'tenant-test'
|
&& $context['tenant_code'] === 'tenant-test'
|
||||||
|
&& $context['channel'] === PasswordResetRequested::CHANNEL_STOREFRONT
|
||||||
&& $context['exception'] === $exception),
|
&& $context['exception'] === $exception),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user