diff --git a/app/Facades/File/File.php b/app/Facades/File/File.php new file mode 100644 index 00000000..79155a8e --- /dev/null +++ b/app/Facades/File/File.php @@ -0,0 +1,107 @@ +putFileAs( + $path, + $file, + $name ?? Str::uuid() . '.' . $file->extension() + ); + } + + /** + * @param string $filePath + * @param bool $url_is_absolute + * @param string $disk + * @return void + * @throws \InvalidArgumentException when filePath is empty. + */ + public function delete(string $filePath, bool $url_is_absolute = false, string $disk = 'public'): void + { + if (!$filePath){ + throw new \InvalidArgumentException('File path is invalid.'); + } + + if ($url_is_absolute) { + $needle = 'storage/'; + $pos = strpos($filePath, $needle); + $filePath = substr($filePath, $pos + strlen($needle)); + } + + if (Storage::disk($disk)->exists($filePath)) { + Storage::disk($disk)->delete($filePath); + } + } + + public function deleteContent($file_path): bool + { + if (file_exists($file_path)) { + file_put_contents($file_path, ''); + + return true; + } + return false; + } + + public function tail($file_path, $lines = 100, $buffer = 2048): bool|string + { + // Open file + $file = @fopen($file_path, "rb"); + if ($file === false) { + return false; + } + + // Jump to last character + fseek($file, -1, SEEK_END); + + // Read it and adjust line number if necessary + // (Otherwise the result would be wrong if file doesn't end with a blank line) + if (fread($file, 1) != "\n") { + $lines -= 1; + } + + // Start reading + $output = ''; + $chunk = ''; + + // While we would like more + while (ftell($file) > 0 && $lines >= 0) { + + // Figure out how far back we should jump + $seek = min(ftell($file), $buffer); + + // Do the jump (backwards, relative to where we are) + fseek($file, -$seek, SEEK_CUR); + + // Read a chunk and prepend it to our output + $output = ($chunk = fread($file, $seek)) . $output; + + // Jump back to where we started reading + fseek($file, -mb_strlen($chunk, '8bit'), SEEK_CUR); + + // Decrease our line counter + $lines -= substr_count($chunk, "\n"); + } + + // While we have too many lines + // (Because of buffer size we might have read too many) + while ($lines++ < 0) { + + // Find first newline and remove all text before that + $output = substr($output, strpos($output, "\n") + 1); + } + + // Close file and return + fclose($file); + return trim($output); + } +} diff --git a/app/Facades/File/FileFacade.php b/app/Facades/File/FileFacade.php new file mode 100644 index 00000000..1c193f86 --- /dev/null +++ b/app/Facades/File/FileFacade.php @@ -0,0 +1,13 @@ +successResponse([ + 'username' => $user->username, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'name' => $user->name, + 'last_login' => $user->last_login, + 'major' => $user->major, + 'degree' => $user->degree, + 'mobile' => $user->mobile, + 'avatar' => $user->avatar, + ]); + } + + public function edit(EditRequest $request): JsonResponse + { + $user = Auth::user(); + + if ($request->has('avatar')) + { + if ($user->avatar) { + FileFacade::delete($user->avatar); + } + $destinationPath = 'avatar/' . $user->username; + $filename = time() .".". $request->file('avatar')->getClientOriginalExtension(); + $avatar = FileFacade::save($request->avatar, $destinationPath, $filename); + } + + $user->update([ + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'major' => $request->major, + 'degree' => $request->degree, + 'mobile' => $request->mobile, + 'avatar' => $avatar ?? $user->avatar + ]); + + return $this->successResponse(); + } + + public function changePassword(ChangePasswordRequest $request): JsonResponse + { + $user = Auth::user(); + + if (! Hash::check($request->current_password, $user->password)) + { + return $this->errorResponse(__('messages.incorrect_current_password')); + } + + $user->update([ + 'password' => Hash::make($request->new_password) + ]); + + return $this->successResponse(); + + } +} diff --git a/app/Http/Requests/V3/Profile/ChangePasswordRequest.php b/app/Http/Requests/V3/Profile/ChangePasswordRequest.php new file mode 100644 index 00000000..9e5762e0 --- /dev/null +++ b/app/Http/Requests/V3/Profile/ChangePasswordRequest.php @@ -0,0 +1,29 @@ +|string> + */ + public function rules(): array + { + return [ + 'current_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + 'new_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + ]; + } +} diff --git a/app/Http/Requests/V3/Profile/EditRequest.php b/app/Http/Requests/V3/Profile/EditRequest.php new file mode 100644 index 00000000..9b4b5d7a --- /dev/null +++ b/app/Http/Requests/V3/Profile/EditRequest.php @@ -0,0 +1,33 @@ +|string> + */ + public function rules(): array + { + return [ + 'first_name' => 'required|string', + 'last_name' => 'required|string', + 'degree' => 'required', + 'major' => 'required', + 'mobile' => 'required|regex:/^09\d{9}$/|numeric|digits:11', + 'avatar' => 'mimes:png,jpg,jpeg|max:2048' + ]; + } +} diff --git a/app/Providers/FileServiceProvider.php b/app/Providers/FileServiceProvider.php new file mode 100644 index 00000000..85822877 --- /dev/null +++ b/app/Providers/FileServiceProvider.php @@ -0,0 +1,30 @@ +app->bind('file', function () { + return new File(); + }); + } + + /** + * Bootstrap services. + * + * @return void + */ + public function boot() + { + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index fb278b54..3c96091a 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -69,8 +69,8 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/v2.php')); Route::prefix('api/v3') - ->name('v3') - ->middleware('web', 'auth', 'confirmUser') + ->name('v3.') + ->middleware(['web', 'auth', 'confirmUser']) ->namespace($this->namespace) ->group(base_path('routes/v3.php')); Route::prefix('api') diff --git a/bootstrap/cache/services.php b/bootstrap/cache/services.php index af155298..13560d20 100755 --- a/bootstrap/cache/services.php +++ b/bootstrap/cache/services.php @@ -45,6 +45,7 @@ 41 => 'App\\Providers\\TelescopeServiceProvider', 42 => 'Barryvdh\\Debugbar\\ServiceProvider', 43 => 'App\\Providers\\DataTableServiceProvider', + 44 => 'App\\Providers\\FileServiceProvider', ), 'eager' => array ( @@ -78,6 +79,7 @@ 27 => 'App\\Providers\\TelescopeServiceProvider', 28 => 'Barryvdh\\Debugbar\\ServiceProvider', 29 => 'App\\Providers\\DataTableServiceProvider', + 30 => 'App\\Providers\\FileServiceProvider', ), 'deferred' => array ( diff --git a/config/app.php b/config/app.php index 417dcd1a..43214a21 100644 --- a/config/app.php +++ b/config/app.php @@ -183,6 +183,7 @@ return [ // custom facades here.. App\Providers\DataTableServiceProvider::class, + App\Providers\FileServiceProvider::class, ], diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index e0cbc784..b07fa76a 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Facades\Hash; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> @@ -23,6 +24,8 @@ class UserFactory extends Factory 'confirmed' => 1, 'province_fa' => $this->faker->name, 'city_fa' => $this->faker->name, + 'password' => Hash::make('password1234'), + 'avatar' => $this->faker->image ]; } } diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php new file mode 100644 index 00000000..0afa8875 --- /dev/null +++ b/resources/lang/en/messages.php @@ -0,0 +1,6 @@ + 'The Operation was successful', + 'incorrect_current_password' => 'The current password is incorrect.', +]; diff --git a/resources/lang/fa/messages.php b/resources/lang/fa/messages.php new file mode 100644 index 00000000..c409d2f5 --- /dev/null +++ b/resources/lang/fa/messages.php @@ -0,0 +1,6 @@ + 'عملیات موفق آمیز بود', + 'incorrect_current_password' => 'رمز عبور فعلی اشتباه است', +]; \ No newline at end of file diff --git a/resources/lang/fa/validation.php b/resources/lang/fa/validation.php index cc8da303..8fc265b8 100644 --- a/resources/lang/fa/validation.php +++ b/resources/lang/fa/validation.php @@ -13,109 +13,109 @@ return [ | */ - "accepted" => ":attribute باید پذیرفته شده باشد.", - "active_url" => "آدرس :attribute معتبر نیست", - "after" => ":attribute باید بعد از :date باشد.", - 'after_or_equal' => ':attribute باید بعد از یا برابر تاریخ :date باشد.', - "alpha" => ":attribute باید شامل حروف الفبا باشد.", - "alpha_dash" => ":attribute باید شامل حروف الفبا و عدد و خظ تیره(-) باشد.", - "alpha_num" => ":attribute باید شامل حروف الفبا و عدد باشد.", - "array" => ":attribute باید شامل آرایه باشد.", - "before" => ":attribute باید تاریخی قبل از :date باشد.", + "accepted" => ":attribute باید پذیرفته شده باشد.", + "active_url" => "آدرس :attribute معتبر نیست", + "after" => ":attribute باید بعد از :date باشد.", + 'after_or_equal' => ':attribute باید بعد از یا برابر تاریخ :date باشد.', + "alpha" => ":attribute باید شامل حروف الفبا باشد.", + "alpha_dash" => ":attribute باید شامل حروف الفبا و عدد و خظ تیره(-) باشد.", + "alpha_num" => ":attribute باید شامل حروف الفبا و عدد باشد.", + "array" => ":attribute باید شامل آرایه باشد.", + "before" => ":attribute باید تاریخی قبل از :date باشد.", 'before_or_equal' => ':attribute باید قبل از یا برابر تاریخ :date باشد.', - "between" => [ + "between" => [ "numeric" => ":attribute باید بین :min و :max باشد.", - "file" => ":attribute باید بین :min و :max کیلوبایت باشد.", - "string" => ":attribute باید بین :min و :max کاراکتر باشد.", - "array" => ":attribute باید بین :min و :max آیتم باشد.", + "file" => ":attribute باید بین :min و :max کیلوبایت باشد.", + "string" => ":attribute باید بین :min و :max کاراکتر باشد.", + "array" => ":attribute باید بین :min و :max آیتم باشد.", ], - "boolean" => "فیلد :attribute فقط میتواند صحیح و یا غلط باشد", - "confirmed" => ":attribute با تاییدیه مطابقت ندارد.", - "date" => ":attribute یک تاریخ معتبر نیست.", - 'date_equals' => ':attribute باید برابر تاریخ :date باشد.', - "date_format" => ":attribute با الگوی :format مطاقبت ندارد.", - "different" => ":attribute و :other باید متفاوت باشند.", - "digits" => ":attribute باید :digits رقم باشد.", - "digits_between" => ":attribute باید بین :min و :max رقم باشد.", - 'dimensions' => 'dimensions مربوط به فیلد :attribute اشتباه است.', - 'distinct' => ':attribute مقدار تکراری دارد.', - "email" => "فرمت :attribute معتبر نیست.", - 'ends_with' => ':attribute باید با این مقدار تمام شود: :values.', - "exists" => ":attribute انتخاب شده، معتبر نیست.", - 'file' => 'فیلد :attribute باید فایل باشد.', - "filled" => "فیلد :attribute الزامی است", + "boolean" => "فیلد :attribute فقط میتواند صحیح و یا غلط باشد", + "confirmed" => ":attribute با تاییدیه مطابقت ندارد.", + "date" => ":attribute یک تاریخ معتبر نیست.", + 'date_equals' => ':attribute باید برابر تاریخ :date باشد.', + "date_format" => ":attribute با الگوی :format مطاقبت ندارد.", + "different" => ":attribute و :other باید متفاوت باشند.", + "digits" => ":attribute باید :digits رقم باشد.", + "digits_between" => ":attribute باید بین :min و :max رقم باشد.", + 'dimensions' => 'dimensions مربوط به فیلد :attribute اشتباه است.', + 'distinct' => ':attribute مقدار تکراری دارد.', + "email" => "فرمت :attribute معتبر نیست.", + 'ends_with' => ':attribute باید با این مقدار تمام شود: :values.', + "exists" => ":attribute انتخاب شده، معتبر نیست.", + 'file' => 'فیلد :attribute باید فایل باشد.', + "filled" => "فیلد :attribute الزامی است", 'gt' => [ 'numeric' => ':attribute باید بیشتر از :value باشد.', - 'file' => ':attribute باید بیشتر از :value کیلوبایت باشد.', - 'string' => ':attribute باید بیشتر از :value کاراکتر باشد.', - 'array' => ':attribute باید بیشتر از :value ایتم باشد.', + 'file' => ':attribute باید بیشتر از :value کیلوبایت باشد.', + 'string' => ':attribute باید بیشتر از :value کاراکتر باشد.', + 'array' => ':attribute باید بیشتر از :value ایتم باشد.', ], 'gte' => [ 'numeric' => ':attribute باید بیشتر یا برابر :value باشد.', - 'file' => ':attribute باید بیشتر یا برابر :value کیلوبایت باشد.', - 'string' => ':attribute باید بیشتر یا برابر :value کاراکتر باشد.', - 'array' => ':attribute باید :value ایتم یا بیشتر را داشته باشد.', + 'file' => ':attribute باید بیشتر یا برابر :value کیلوبایت باشد.', + 'string' => ':attribute باید بیشتر یا برابر :value کاراکتر باشد.', + 'array' => ':attribute باید :value ایتم یا بیشتر را داشته باشد.', ], - "image" => ":attribute باید تصویر باشد.", - "in" => ":attribute انتخاب شده، معتبر نیست.", - "integer" => ":attribute باید نوع داده ای عددی (integer) باشد.", - "ip" => ":attribute باید IP آدرس معتبر باشد.", - 'ipv4' => ':attribute باید یک ادرس درست IPv4 باشد.', - 'ipv6' => ':attribute باید یک ادرس درست IPv6 باشد.', - 'json' => ':attribute یک مقدار درست JSON باشد.', + "image" => ":attribute باید تصویر باشد.", + "in" => ":attribute انتخاب شده، معتبر نیست.", + "integer" => ":attribute باید نوع داده ای عددی (integer) باشد.", + "ip" => ":attribute باید IP آدرس معتبر باشد.", + 'ipv4' => ':attribute باید یک ادرس درست IPv4 باشد.', + 'ipv6' => ':attribute باید یک ادرس درست IPv6 باشد.', + 'json' => ':attribute یک مقدار درست JSON باشد.', 'lt' => [ 'numeric' => ':attribute باید کمتر از :value باشد.', - 'file' => ':attribute باید کمتر از :value کیلوبایت باشد.', - 'string' => ':attribute باید کمتر از :value کاراکتر باشد.', - 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', + 'file' => ':attribute باید کمتر از :value کیلوبایت باشد.', + 'string' => ':attribute باید کمتر از :value کاراکتر باشد.', + 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', ], 'lte' => [ 'numeric' => ':attribute باید کمتر یا برابر :value باشد.', - 'file' => ':attribute باید کمتر یا برابر :value کیلوبایت باشد.', - 'string' => ':attribute باید کمتر یا برابر :value کاراکتر باشد.', - 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', + 'file' => ':attribute باید کمتر یا برابر :value کیلوبایت باشد.', + 'string' => ':attribute باید کمتر یا برابر :value کاراکتر باشد.', + 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', ], - "max" => [ + "max" => [ "numeric" => ":attribute نباید بزرگتر از :max باشد.", - "file" => ":attribute نباید بزرگتر از :max کیلوبایت باشد.", - "string" => ":attribute نباید بیشتر از :max کاراکتر باشد.", - "array" => ":attribute نباید بیشتر از :max آیتم باشد.", + "file" => ":attribute نباید بزرگتر از :max کیلوبایت باشد.", + "string" => ":attribute نباید بیشتر از :max کاراکتر باشد.", + "array" => ":attribute نباید بیشتر از :max آیتم باشد.", ], - "mimes" => ":attribute باید یکی از فرمت های :values باشد.", - 'mimetypes' => ':attribute باید تایپ ان از نوع: :values باشد.', - "min" => [ + "mimes" => ":attribute باید یکی از فرمت های :values باشد.", + 'mimetypes' => ':attribute باید تایپ ان از نوع: :values باشد.', + "min" => [ "numeric" => ":attribute نباید کوچکتر از :min باشد.", - "file" => ":attribute نباید کوچکتر از :min کیلوبایت باشد.", - "string" => ":attribute نباید کمتر از :min کاراکتر باشد.", - "array" => ":attribute نباید کمتر از :min آیتم باشد.", + "file" => ":attribute نباید کوچکتر از :min کیلوبایت باشد.", + "string" => ":attribute نباید کمتر از :min کاراکتر باشد.", + "array" => ":attribute نباید کمتر از :min آیتم باشد.", ], - "not_in" => ":attribute انتخاب شده، معتبر نیست.", - 'not_regex' => ':attribute فرمت معتبر نیست.', - "numeric" => ":attribute باید شامل عدد باشد.", - 'password' => 'رمز عبور اشتباه است.', - 'present' => ':attribute باید وجود داشته باشد.', - "regex" => ":attribute یک فرمت معتبر نیست", - "required" => "فیلد :attribute الزامی است", - "required_if" => "فیلد :attribute هنگامی که :other برابر با :value است، الزامیست.", - 'required_unless' => 'قیلد :attribute الزامیست مگر این فیلد :other مقدارش :values باشد.', - "required_with" => ":attribute الزامی است زمانی که :values موجود است.", + "not_in" => ":attribute انتخاب شده، معتبر نیست.", + 'not_regex' => ':attribute فرمت معتبر نیست.', + "numeric" => ":attribute باید شامل عدد باشد.", + 'password' => 'رمز عبور اشتباه است.', + 'present' => ':attribute باید وجود داشته باشد.', + "regex" => ":attribute یک فرمت معتبر نیست", + "required" => "فیلد :attribute الزامی است", + "required_if" => "فیلد :attribute هنگامی که :other برابر با :value است، الزامیست.", + 'required_unless' => 'قیلد :attribute الزامیست مگر این فیلد :other مقدارش :values باشد.', + "required_with" => ":attribute الزامی است زمانی که :values موجود است.", "required_with_all" => ":attribute الزامی است زمانی که :values موجود است.", "required_without" => ":attribute الزامی است زمانی که :values موجود نیست.", "required_without_all" => ":attribute الزامی است زمانی که :values موجود نیست.", - "same" => ":attribute و :other باید مانند هم باشند.", - "size" => [ + "same" => ":attribute و :other باید مانند هم باشند.", + "size" => [ "numeric" => ":attribute باید برابر با :size باشد.", - "file" => ":attribute باید برابر با :size کیلوبایت باشد.", - "string" => ":attribute باید برابر با :size کاراکتر باشد.", - "array" => ":attribute باسد شامل :size آیتم باشد.", + "file" => ":attribute باید برابر با :size کیلوبایت باشد.", + "string" => ":attribute باید برابر با :size کاراکتر باشد.", + "array" => ":attribute باسد شامل :size آیتم باشد.", ], - 'starts_with' => ':attribute باید با یکی از این مقادیر شروع شود: :values.', - "string" => ":attribute باید رشته باشد.", - "timezone" => "فیلد :attribute باید یک منطقه صحیح باشد.", - "unique" => ":attribute قبلا انتخاب شده است.", - 'uploaded' => 'فیلد :attribute به درستی اپلود نشد.', - "url" => "فرمت آدرس :attribute اشتباه است.", - 'uuid' => ':attribute باید یک فرمت درست UUID باشد.', + 'starts_with' => ':attribute باید با یکی از این مقادیر شروع شود: :values.', + "string" => ":attribute باید رشته باشد.", + "timezone" => "فیلد :attribute باید یک منطقه صحیح باشد.", + "unique" => ":attribute قبلا انتخاب شده است.", + 'uploaded' => 'فیلد :attribute به درستی اپلود نشد.', + "url" => "فرمت آدرس :attribute اشتباه است.", + 'uuid' => ':attribute باید یک فرمت درست UUID باشد.', /* |-------------------------------------------------------------------------- @@ -173,7 +173,12 @@ return [ "time" => "زمان", "available" => "موجود", "size" => "اندازه", - "file" => "فایل", - "fullname" => "نام کامل" + "file" => "فایل", + "fullname" => "نام کامل", + 'current_password' => 'رمز عبور فعلی', + 'new_password' => 'رمز عبور جدید', + 'degree' => 'مدرک تحصیلی', + 'major' => 'شغل', + 'avatar' => 'آواتار' ], ]; diff --git a/routes/v3.php b/routes/v3.php index 6f8f2803..9597fc93 100644 --- a/routes/v3.php +++ b/routes/v3.php @@ -1,6 +1,8 @@ name('harim')->group(function () { Route::prefix('divarkeshi')->name('divarkeshi')->controller(DivarkeshiController::class)->group(function () { @@ -21,3 +23,12 @@ Route::prefix('harim')->name('harim')->group(function () { // Route::get('/{contract}', 'V3\Dashboard\ContractController@detail')->name('V3.contract.detail'); }); }); + +Route::prefix('profile') + ->name('profile.') + ->controller(ProfileController::class) + ->group(function () { + Route::get('info', 'info')->name('info'); + Route::post('edit', 'edit')->name('edit'); + Route::post('change_password', 'changePassword')->name('changePassword'); + }); diff --git a/tests/Feature/Facades/FileTest.php b/tests/Feature/Facades/FileTest.php new file mode 100644 index 00000000..17716fa0 --- /dev/null +++ b/tests/Feature/Facades/FileTest.php @@ -0,0 +1,115 @@ +image($this->faker->image); + $path = FileFacade::save($inputFile, 'testDir'); + + Storage::disk('public')->assertExists($path); + } + + public function test_image_is_saved_with_specified_name(): void + { + Storage::fake('public'); + + $imageName = 'photo.png'; + $inputFile = UploadedFile::fake()->image($imageName); + $actualPath = FileFacade::save($inputFile, 'testDir', $imageName); + $expectedPath = 'testDir/photo.png'; + + Storage::disk('public')->assertExists($actualPath); + $this->assertEquals($expectedPath, $actualPath); + } + + public function test_file_is_saved(): void + { + Storage::fake('public'); + + $fileName = 'fake' . '.' . $this->faker->fileExtension(); + $inputFile = UploadedFile::fake()->create($fileName); + $path = FileFacade::save($inputFile, 'testDir'); + + Storage::disk('public')->assertExists($path); + } + + public function test_file_is_saved_with_specified_name(): void + { + Storage::fake('public'); + + $fileName = 'fake' . '.' . $this->faker->fileExtension(); + $inputFile = UploadedFile::fake()->create($fileName); + $actualPath = FileFacade::save($inputFile, 'testDir', $fileName); + $expectedPath = 'testDir/' . $fileName; + + Storage::disk('public')->assertExists($actualPath); + $this->assertEquals($expectedPath, $actualPath); + } + + public function test_file_is_saved_with_specified_disk(): void + { + Storage::fake('public'); + Storage::fake('p2'); + + $fileName = 'fake' . '.' . $this->faker->fileExtension(); + $inputFile = UploadedFile::fake()->create($fileName); + $path = FileFacade::save($inputFile, 'testDir', disk: 'p2'); + + Storage::disk('p2')->assertExists($path); + Storage::disk('public')->assertMissing($path); + } + + public function test_delete_throws_exception_with_empty_file_path(): void + { + $this->assertThrows( + fn() => FileFacade::delete(''), + \InvalidArgumentException::class + ); + } + + public function test_file_is_deleted(): void + { + Storage::fake('public'); + + $fileName = 'fake' . '.' . $this->faker->fileExtension(); + $inputFile = UploadedFile::fake()->create($fileName); + + $path = Storage::disk('public')->putFile('testDir', $inputFile); + + Storage::disk('public')->assertExists($path); + FileFacade::delete($path); + Storage::disk('public')->assertMissing($path); + } + + public function test_file_with_absolute_path_is_deleted(): void + { + Storage::fake('public'); + + $fileName = 'fake' . '.' . $this->faker->fileExtension(); + $inputFile = UploadedFile::fake()->create($fileName); + + $path = Storage::disk('public')->putFile('testDir', $inputFile); + $absolutePath = Storage::disk('public')->url($path); + + Storage::disk('public')->assertExists($path); + FileFacade::delete($absolutePath, url_is_absolute: true); + Storage::disk('public')->assertMissing($path); + } +} diff --git a/tests/Feature/V3/Profile/ChangePasswordTest.php b/tests/Feature/V3/Profile/ChangePasswordTest.php new file mode 100644 index 00000000..59691a2e --- /dev/null +++ b/tests/Feature/V3/Profile/ChangePasswordTest.php @@ -0,0 +1,180 @@ +create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'current_password' => __('validation.required', ['attribute' => 'رمز عبور فعلی']) + ]); + } + + public function test_current_password_should_be_string() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword'), [ + 'current_password' => $this->faker->numberBetween(1, 10) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'current_password' => __('validation.string', ['attribute' => 'رمز عبور فعلی']) + ]); + } + + public function test_current_password_should_match_the_regex() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword'), [ + 'current_password' => $this->faker->numberBetween(10000000, 99999999) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'current_password' => __('validation.regex', ['attribute' => 'رمز عبور فعلی']) + ]); + } + + public function test_current_password_should_have_at_least_8_characters() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword'), [ + 'current_password' => $this->faker->numerify() + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'current_password' => __('validation.min.string', ['attribute' => 'رمز عبور فعلی', 'min' => 8]) + ]); + } + + public function test_new_password_is_required() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'new_password' => __('validation.required', ['attribute' => 'رمز عبور جدید']) + ]); + } + + public function test_new_password_should_be_string() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword'), [ + 'new_password' => $this->faker->numberBetween(1, 10) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'new_password' => __('validation.string', ['attribute' => 'رمز عبور جدید']) + ]); + } + + public function test_new_password_should_match_the_regex() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword'), [ + 'new_password' => $this->faker->numberBetween(10000000, 99999999) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'new_password' => __('validation.regex', ['attribute' => 'رمز عبور جدید']) + ]); + } + + public function test_new_password_should_have_at_least_8_characters() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.changePassword'), [ + 'new_password' => $this->faker->numerify() + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'new_password' => __('validation.min.string', ['attribute' => 'رمز عبور جدید', 'min' => 8]) + ]); + } + + public function test_can_not_change_the_password_of_the_user_with_wrong_current_password(): void + { + $this->withoutExceptionHandling(); + + $user = User::factory()->create(); + + $this->actingAs($user); + + $password = $this->faker->numerify('#a######'); + + $response = $this->post(route('v3.profile.changePassword'), [ + 'current_password' => 'fake1234', + 'new_password' => $password, + ]); + + $response->assertStatus(422); + + $response->assertExactJson([ + 'message' => __('messages.incorrect_current_password'), + "type" => "logical_exception" + ]); + } + + public function test_user_can_change_the_password(): void + { + $this->withoutExceptionHandling(); + + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->post(route('v3.profile.changePassword'), [ + 'current_password' => 'password1234', + 'new_password' => $this->faker->numerify('#a######'), + ]); + + $response->assertStatus(200); + + $response->assertExactJson([ + 'message' => __('messages.successful') + ]); + } +} diff --git a/tests/Feature/V3/Profile/EditTest.php b/tests/Feature/V3/Profile/EditTest.php new file mode 100644 index 00000000..6f2b60c7 --- /dev/null +++ b/tests/Feature/V3/Profile/EditTest.php @@ -0,0 +1,239 @@ +create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'first_name' => __('validation.required', ['attribute' => 'نام']) + ]); + } + + public function test_first_name_should_be_string() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'first_name' => $this->faker->numberBetween(0, 10) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'first_name' => __('validation.string', ['attribute' => 'نام']) + ]); + } + + public function test_last_name_is_required() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'last_name' => __('validation.required', ['attribute' => 'نام خانوادگی']) + ]); + } + + public function test_last_name_should_be_string() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'last_name' => $this->faker->numberBetween(0, 10) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'last_name' => __('validation.string', ['attribute' => 'نام خانوادگی']) + ]); + } + + public function test_degree_is_required() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'degree' => __('validation.required', ['attribute' => 'مدرک تحصیلی']) + ]); + } + + public function test_major_is_required() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'major' => __('validation.required', ['attribute' => 'شغل']) + ]); + } + + public function test_mobile_is_required() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'mobile' => __('validation.required', ['attribute' => 'تلفن همراه']) + ]); + } + + public function test_mobile_should_match_the_regex() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'mobile' => $this->faker->numberBetween(1000000, 9999999) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'mobile' => __('validation.regex', ['attribute' => 'تلفن همراه']) + ]); + } + + public function test_mobile_should_be_numeric() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'mobile' => $this->faker->name() + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'mobile' => __('validation.numeric', ['attribute' => 'تلفن همراه']) + ]); + } + + public function test_mobile_should_be_11_digits() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'mobile' => $this->faker->numberBetween(10000, 99999) + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'mobile' => __('validation.digits', ['attribute' => 'تلفن همراه', 'digits' => 11]) + ]); + } + + public function test_avatar_should_match_the_mimes() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $image = UploadedFile::fake()->create('image.txt', 40); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'avatar' => $image + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'avatar' => __('validation.mimes', ['attribute' => 'آواتار', 'values' => 'png, jpg, jpeg']) + ]); + } + + public function test_avatar_should_be_less_than_2048kb() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $image = UploadedFile::fake()->create('image.jpg', 40000); + + $response = $this->postJson(route('v3.profile.edit'), [ + 'avatar' => $image + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'avatar' => __('validation.max.file', ['attribute' => 'آواتار', 'max' => 2048]) + ]); + } + + public function test_user_can_edit_the_profile() + { + $user = User::factory()->create(); + + $this->actingAs($user); + + $avatar = UploadedFile::fake()->create('image.jpg', 1024); + + FileFacade::shouldReceive('save') + ->andReturn('image.jpg'); + + FileFacade::shouldReceive('delete'); + + $data = [ + 'first_name' => $this->faker->firstName, + 'last_name' => $this->faker->lastName, + 'degree' => $this->faker->name, + 'major' => $this->faker->name, + 'mobile' => $this->faker->numerify('09#########'), + 'avatar' => $avatar + ]; + + $response = $this->postJson(route('v3.profile.edit'), $data); + + $response->assertOk() + ->assertExactJson([ + 'message' => __('messages.successful') + ]); + + $this->assertDatabaseHas('users', [ + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'mobile' => $data['mobile'], + 'degree' => $data['degree'], + ]); + } +} diff --git a/tests/Feature/V3/Profile/InfoTest.php b/tests/Feature/V3/Profile/InfoTest.php new file mode 100644 index 00000000..bf69b72a --- /dev/null +++ b/tests/Feature/V3/Profile/InfoTest.php @@ -0,0 +1,38 @@ +create(); + + $this->actingAs($user); + + $response = $this->get(route('v3.profile.info')); + + $response->assertStatus(200); + + $response->assertExactJson([ + 'data' => [ + 'username' => $user->username, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'name' => $user->name, + 'last_login' => $user->last_login, + 'major' => $user->major, + 'degree' => $user->degree, + 'mobile' => $user->mobile, + 'avatar' => $user->avatar, + ] + ]); + } +}