Skip to content

Commit 86100ec

Browse files
committed
Add tests
1 parent 612a6c7 commit 86100ec

25 files changed

+627
-334
lines changed
File renamed without changes.

bootstrap/app.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
declare(strict_types=1);
44

5+
use Illuminate\Database\Eloquent\ModelNotFoundException;
56
use Illuminate\Foundation\Application;
67
use Illuminate\Foundation\Configuration\Exceptions;
78
use Illuminate\Foundation\Configuration\Middleware;
9+
use Illuminate\Http\Request;
810
use Larowka\PreventDuplicateRequests\Middleware\PreventDuplicateRequests;
911
use Shared\Helpers\ResponseHelper;
1012
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -28,12 +30,15 @@
2830
$middleware->alias([
2931
'verified' => \App\Http\Middleware\EnsureEmailIsVerified::class,
3032
]);
31-
3233
$middleware->append(PreventDuplicateRequests::class);
33-
3434
})
3535
->withExceptions(function (Exceptions $exceptions): void {
36-
$exceptions->renderable(function (NotFoundHttpException $e) {
36+
// customize response for 404 error for route and resource
37+
$exceptions->renderable(function (NotFoundHttpException $e, Request $request) {
38+
if (($previous = $e->getPrevious()) instanceof ModelNotFoundException && $request->expectsJson()) {
39+
return ResponseHelper::error(class_basename($previous->getModel()) . ' Not Found.', status: 404);
40+
}
41+
3742
return ResponseHelper::error(message: 'Route not found', status: 404);
3843
});
3944

config/constants.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
$application = env('FRONTEND_URL', 'http://localhost');
3+
4+
return [
5+
// Pagination number
6+
'per_page' => 10,
7+
'user_dashboard' => $application.'/dashboard',
8+
'email_verification_link' => '/auth/login'
9+
];
10+

database/factories/RoleFactory.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Database\Factories;
6+
7+
use Illuminate\Database\Eloquent\Factories\Factory;
8+
use Illuminate\Database\Eloquent\Factories\HasFactory;
9+
use Modules\V1\User\Models\Role;
10+
11+
/**
12+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Modules\V1\User\Models\Role>
13+
*/
14+
final class RoleFactory extends Factory
15+
{
16+
use HasFactory;
17+
protected $model = Role::class;
18+
19+
/**
20+
* The current password being used by the factory.
21+
*/
22+
protected static ?string $password;
23+
24+
/**
25+
* Define the model's default state.
26+
*
27+
* @return array<string, mixed>
28+
*/
29+
public function definition(): array
30+
{
31+
return [
32+
'name' => $this->faker->word,
33+
];
34+
}
35+
36+
/**
37+
* Indicate that the model's email address should be unverified.
38+
*/
39+
public function unverified(): static
40+
{
41+
return $this->state(fn (array $attributes) => [
42+
'email_verified_at' => null,
43+
]);
44+
}
45+
}

database/factories/UserFactory.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace Database\Factories;
46

57
use Illuminate\Database\Eloquent\Factories\Factory;
@@ -11,7 +13,7 @@
1113
/**
1214
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Modules\V1\User\Models\User>
1315
*/
14-
class UserFactory extends Factory
16+
final class UserFactory extends Factory
1517
{
1618
protected $model = User::class;
1719

@@ -32,7 +34,7 @@ public function definition(): array
3234
'email' => fake()->unique()->safeEmail(),
3335
'role_id' => RoleEnum::USER->value,
3436
'email_verified_at' => now(),
35-
'password' => static::$password ??= Hash::make('password'),
37+
'password' => self::$password ??= Hash::make('password'),
3638
'remember_token' => Str::random(10),
3739
];
3840
}

phpunit.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
2323
<env name="BCRYPT_ROUNDS" value="4"/>
2424
<env name="CACHE_STORE" value="array"/>
25-
<env name="DB_CONNECTION" value="pgsql"/>
26-
<!-- <env name="DB_DATABASE" value=":memory:"/>-->
25+
<env name="DB_CONNECTION" value="sqlite"/>
26+
<env name="DB_DATABASE" value=":memory:"/>
2727
<env name="MAIL_MAILER" value="array"/>
2828
<env name="PULSE_ENABLED" value="false"/>
2929
<env name="QUEUE_CONNECTION" value="sync"/>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@include('email.inc.header')
2+
3+
<div class="content-body">
4+
<img class="header-logo" src="{{ asset('logo.png') }}" alt="Logo">
5+
6+
<h1>Welcome</h1>
7+
<p>Dear {{ $name }},</p>
8+
<p>We welcome you</p>
9+
<a href="{{ $dashboardLink }}" class="button">Dashboard</a>
10+
</div>
11+
12+
@include('email.inc.footer')

src/modules/V1/Auth/Controllers/NewPasswordController.php

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Modules\V1\Auth\Controllers;
44

5+
use Illuminate\Contracts\Encryption\DecryptException;
6+
use Illuminate\Support\Facades\Log;
7+
use Modules\V1\Auth\Requests\ResetPasswordRequest;
58
use Shared\Helpers\GlobalHelper;
69
use Shared\Helpers\ResponseHelper;
710
use App\Http\Controllers\V1\Controller;
@@ -121,31 +124,35 @@ class NewPasswordController extends Controller
121124
* )
122125
* )
123126
*/
124-
public function store(Request $request): JsonResponse
127+
public function store(ResetPasswordRequest $request): JsonResponse
125128
{
126-
$request->validate([
127-
'token' => ['required', 'string'],
128-
'password' => ['required', 'confirmed', Rules\Password::defaults()],
129-
]);
129+
try {
130+
$token = GlobalHelper::decrypt($request->token);
130131

131-
$token = GlobalHelper::decrypt($request->token);
132+
// Find the user by the verification token
133+
$user = User::where('verification_token', $token)->first();
132134

133-
// Find the user by the verification token
134-
$user = User::where('verification_token', $token)->first();
135+
if (!$user) {
136+
return ResponseHelper::error('Invalid verification token', 404);
137+
}
135138

136-
if (!$user) {
137-
return ResponseHelper::error('Invalid verification token', 404);
138-
}
139+
// Check if the token has expired
140+
if ($user->verification_token_expiry && (new Carbon($user->verification_token_expiry))->isPast()) {
141+
return ResponseHelper::error('Verification token has expired', 400);
142+
}
139143

140-
// Check if the token has expired
141-
if ($user->verification_token_expiry && (new Carbon($user->verification_token_expiry))->isPast()) {
142-
return ResponseHelper::error('Verification token has expired', 400);
143-
}
144+
// Change user's password
145+
$user->password = Hash::make($request->password);
146+
$user->save();
144147

145-
// Change user's password
146-
$user->password = Hash::make($request->password);
147-
$user->save();
148+
return ResponseHelper::success(message: 'Password changed successfully');
148149

149-
return ResponseHelper::success(message: 'Password changed successfully');
150+
}catch (DecryptException $e) {
151+
Log::error('Invalid decryption token: ' . $e->getMessage());
152+
return ResponseHelper::error('Invalid verification token', 422); // or throw a custom exception
153+
}catch (\Exception $e) {
154+
Log::error($e);
155+
return ResponseHelper::error();
156+
}
150157
}
151158
}

src/modules/V1/Auth/Controllers/VerifyEmailController.php

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,52 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace Modules\V1\Auth\Controllers;
46

5-
use Shared\Helpers\GlobalHelper;
6-
use Shared\Helpers\ResponseHelper;
77
use App\Http\Controllers\V1\Controller;
8+
use Exception;
9+
use Illuminate\Contracts\Encryption\DecryptException;
810
use Illuminate\Http\Request;
911
use Illuminate\Support\Carbon;
10-
use Illuminate\Support\Facades\Mail;
12+
use Illuminate\Support\Facades\Log;
1113
use Illuminate\Support\Str;
12-
use Modules\V1\Auth\Mail\WelcomeMail;
14+
use Modules\V1\Auth\Notifications\Welcome;
1315
use Modules\V1\User\Models\User;
1416
use Modules\V1\User\Resources\UserResource;
17+
use Shared\Helpers\GlobalHelper;
18+
use Shared\Helpers\ResponseHelper;
1519

16-
class VerifyEmailController extends Controller
20+
final class VerifyEmailController extends Controller
1721
{
1822
/**
1923
* @OA\Post(
2024
* path="/auth/email/verify",
2125
* summary="Verify user email",
2226
* tags={"Authentication"},
27+
*
2328
* @OA\RequestBody(
2429
* required=true,
2530
* description="Request body containing the verification token",
31+
*
2632
* @OA\JsonContent(
2733
* required={"token"},
34+
*
2835
* @OA\Property(
2936
* property="token",
3037
* type="string",
3138
* description="Verification token"
3239
* )
3340
* )
3441
* ),
42+
*
3543
* @OA\Response(
3644
* response=200,
3745
* description="Successful operation",
46+
*
3847
* @OA\JsonContent(
3948
* type="object",
49+
*
4050
* @OA\Property(
4151
* property="message",
4252
* type="string",
@@ -57,11 +67,14 @@ class VerifyEmailController extends Controller
5767
* )
5868
* )
5969
* ),
70+
*
6071
* @OA\Response(
6172
* response=400,
6273
* description="Bad request",
74+
*
6375
* @OA\JsonContent(
6476
* type="object",
77+
*
6578
* @OA\Property(
6679
* property="message",
6780
* type="string",
@@ -82,11 +95,14 @@ class VerifyEmailController extends Controller
8295
* )
8396
* )
8497
* ),
98+
*
8599
* @OA\Response(
86100
* response=404,
87101
* description="Not found",
102+
*
88103
* @OA\JsonContent(
89104
* type="object",
105+
*
90106
* @OA\Property(
91107
* property="message",
92108
* type="string",
@@ -111,42 +127,54 @@ class VerifyEmailController extends Controller
111127
*/
112128
public function __invoke(Request $request): \Illuminate\Http\JsonResponse
113129
{
114-
$request->validate([
115-
'token' => ['required', 'string'],
116-
]);
130+
try {
131+
$request->validate([
132+
'token' => ['required', 'string'],
133+
]);
117134

118-
$token = GlobalHelper::decrypt($request->token);
119-
// Find the user by the verification token
120-
$user = User::where('verification_token', $token)->first();
135+
$token = GlobalHelper::decrypt($request->token);
136+
// Find the user by the verification token
137+
$user = User::where('verification_token', $token)->first();
121138

122-
if (!$user) {
123-
return ResponseHelper::error('Invalid verification token', 404);
124-
}
139+
if ( ! $user) {
140+
return ResponseHelper::error('Invalid verification token', 404);
141+
}
125142

126-
// Check if the token has expired
127-
if ($user->verification_token_expiry && (new Carbon($user->verification_token_expiry))->isPast()) {
128-
return ResponseHelper::error('Verification token has expired', 400);
129-
}
143+
// Check if the token has expired
144+
if ($user->verification_token_expiry && (new Carbon($user->verification_token_expiry))->isPast()) {
145+
return ResponseHelper::error('Verification token has expired', 400);
146+
}
130147

131-
if ($user->hasVerifiedEmail()) {
132-
return ResponseHelper::error('Email already verified', 400);
133-
}
148+
if ($user->hasVerifiedEmail()) {
149+
return ResponseHelper::error('Email already verified', 400);
150+
}
134151

135-
if (!$user->markEmailAsVerified()) {
136-
return ResponseHelper::error('Failed to verify email');
137-
}
152+
if ( ! $user->markEmailAsVerified()) {
153+
return ResponseHelper::error('Failed to verify email');
154+
}
155+
156+
// send welcome notification
157+
$user->notify(new Welcome($user, config('constants.user_dashboard')));
138158

139-
Mail::send(new WelcomeMail($user, config('constants.user_dashboard')));
159+
$device = Str::limit($request->userAgent(), 255);
160+
$token = $user->createToken($device)->plainTextToken;
140161

141-
$device = Str::limit($request->userAgent(), 255);
142-
$token = $user->createToken($device)->plainTextToken;
162+
return response()->json([
163+
'message' => 'User verified successfully',
164+
'status' => 'success',
165+
'statusCode' => '200',
166+
'accessToken' => $token,
167+
'data' => new UserResource($user),
168+
]);
169+
} catch (DecryptException $e) {
170+
Log::error('Invalid decryption token: ' . $e);
171+
172+
return ResponseHelper::error('Invalid verification token', 422); // or throw a custom exception
173+
} catch (Exception $exception) {
174+
Log::error($exception);
175+
176+
return ResponseHelper::error();
177+
}
143178

144-
return response()->json([
145-
'message' => 'User verified successfully',
146-
'status' => 'success',
147-
'statusCode' => '200',
148-
'accessToken' => $token,
149-
'data' => new UserResource($user)
150-
]);
151179
}
152180
}

0 commit comments

Comments
 (0)