diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 60ef33e5..cb01f5ce 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -62,16 +62,16 @@ class UserController extends Controller /** * Handle user signing. * - * @return void + * @return \Illuminate\Http\JsonResponse */ public function sign() { if ($this->user->canSign()) { - $acuiredScore = $this->user->sign(); + $acquiredScore = $this->user->sign(); return json([ 'errno' => 0, - 'msg' => trans('user.sign-success', ['score' => $acuiredScore]), + 'msg' => trans('user.sign-success', ['score' => $acquiredScore]), 'score' => $this->user->getScore(), 'storage' => $this->calculatePercentageUsed($this->user->getStorageUsed(), option('score_per_storage')), 'remaining_time' => $this->getUserSignRemainingTimeWithPrecision() @@ -79,7 +79,8 @@ class UserController extends Controller } else { $remaining_time = $this->getUserSignRemainingTimeWithPrecision(); return json(trans('user.cant-sign-until', [ - 'time' => $remaining_time >= 1 ?: round($remaining_time * 60), + 'time' => $remaining_time >= 1 + ? $remaining_time : round($remaining_time * 60), 'unit' => $remaining_time >= 1 ? trans('user.time-unit-hour') : trans('user.time-unit-min') ]), 1); @@ -102,6 +103,7 @@ class UserController extends Controller * Handle changing user profile. * * @param Request $request + * @param UserRepository $users * @return mixed */ public function handleProfile(Request $request, UserRepository $users) @@ -116,10 +118,12 @@ class UserController extends Controller $nickname = $request->input('new_nickname'); - if ($this->user->setNickName($nickname)) + if ($this->user->setNickName($nickname)) { + event(new UserProfileUpdated($action, $this->user)); return json(trans('user.profile.nickname.success', ['nickname' => $nickname]), 0); + } - break; + break; // @codeCoverageIgnore case 'password': $this->validate($request, [ @@ -130,10 +134,12 @@ class UserController extends Controller if (!$this->user->verifyPassword($request->input('current_password'))) return json(trans('user.profile.password.wrong-password'), 1); - if ($this->user->changePasswd($request->input('new_password'))) + if ($this->user->changePasswd($request->input('new_password'))) { + event(new UserProfileUpdated($action, $this->user)); return json(trans('user.profile.password.success'), 0); + } - break; + break; // @codeCoverageIgnore case 'email': $this->validate($request, [ @@ -148,10 +154,12 @@ class UserController extends Controller if (!$this->user->verifyPassword($request->input('password'))) return json(trans('user.profile.email.wrong-password'), 1); - if ($this->user->setEmail($request->input('new_email'))) + if ($this->user->setEmail($request->input('new_email'))) { + event(new UserProfileUpdated($action, $this->user)); return json(trans('user.profile.email.success'), 0); + } - break; + break; // @codeCoverageIgnore case 'delete': $this->validate($request, [ @@ -162,24 +170,24 @@ class UserController extends Controller return json(trans('user.profile.delete.wrong-password'), 1); if ($this->user->delete()) { - setcookie('uid', '', time() - 3600, '/'); - setcookie('token', '', time() - 3600, '/'); - session()->flush(); - return json(trans('user.profile.delete.success'), 0); + return response() + ->json([ + 'errno' => 0, + 'msg' => trans('user.profile.delete.success') + ]) + ->cookie('uid', '', time() - 3600, '/') + ->cookie('token', '', time() - 3600, '/'); } - break; + break; // @codeCoverageIgnore default: return json(trans('general.illegal-parameters'), 1); break; } - - event(new UserProfileUpdated($action, $this->user)); - - } + } // @codeCoverageIgnore /** * Set user avatar. @@ -204,6 +212,6 @@ class UserController extends Controller } else { return json(trans('skinlib.non-existent'), 1); } - } + } // @codeCoverageIgnore } diff --git a/app/Models/User.php b/app/Models/User.php index 3531a20b..5f7b6d1f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -168,7 +168,8 @@ class User extends Model /** * Set new email for user. * - * @param string $new_email + * @param string $new_email + * @return bool */ public function setEmail($new_email) { diff --git a/tests/UserControllerTest.php b/tests/UserControllerTest.php new file mode 100644 index 00000000..0821f571 --- /dev/null +++ b/tests/UserControllerTest.php @@ -0,0 +1,449 @@ +actAs('normal'); + } + + public function testIndex() + { + $user = factory(User::class)->create(); + factory(\App\Models\Player::class)->create(['uid' => $user->uid]); + + $players_count = option('score_per_player') / option('user_initial_score'); + $this->actAs($user) + ->visit('/user') + ->assertViewHas('user') + ->assertViewHas('statistics') + ->see(1 / $players_count * 100) // Players + ->see(0) // Storage + ->see(bs_announcement()) + ->see($user->score); + } + + public function testSign() + { + option(['sign_score' => '50,50']); + $user = factory(User::class)->create(); + + // Success + $this->actAs($user) + ->post('/user/sign') + ->seeJson([ + 'errno' => 0, + 'msg' => trans('user.sign-success', ['score' => 50]), + 'score' => option('user_initial_score') + 50, + 'storage' => [ + 'percentage' => 0, + 'total' => option('user_initial_score') + 50, + 'used' => 0 + ], + 'remaining_time' => (int) option('sign_gap_time') + ]); + + // Remaining time is greater than 0 + $this->post('/user/sign') + ->seeJson([ + 'errno' => 1, + 'msg' => trans( + 'user.cant-sign-until', + [ + 'time' => option('sign_gap_time'), + 'unit' => trans('user.time-unit-hour') + ] + ) + ]); + + // Can sign after 0 o'clock + option(['sign_after_zero' => true]); + $diff = \Carbon\Carbon::now()->diffInSeconds(\Carbon\Carbon::tomorrow()); + $unit = ''; + if ($diff / 3600 >= 1) { + $diff = round($diff / 3600); + $unit = 'hour'; + } else { + $diff = round($diff / 60); + $unit = 'min'; + } + $this->post('/user/sign') + ->seeJson([ + 'errno' => 1, + 'msg' => trans( + 'user.cant-sign-until', + [ + 'time' => $diff, + 'unit' => trans("user.time-unit-$unit") + ] + ) + ]); + + $user->last_sign_at = \Carbon\Carbon::today()->toDateTimeString(); + $user->save(); + $this->post('/user/sign') + ->seeJson([ + 'errno' => 0 + ]); + } + + public function testProfile() + { + $this->visit('/user/profile') + ->assertViewHas('user'); + } + + public function testHandleProfile() + { + $user = factory(User::class)->create(); + $user->changePasswd('12345678'); + + // Invalid action + $this->actAs($user) + ->post('/user/profile') + ->seeJson([ + 'errno' => 1, + 'msg' => trans('general.illegal-parameters') + ]); + + // Change nickname without `new_nickname` field + $this->post('/user/profile', [ + 'action' => 'nickname' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.required', ['attribute' => 'new nickname']) + ]); + + // Invalid nickname + $this->post('/user/profile', [ + 'action' => 'nickname', + 'new_nickname' => '\\' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.nickname', ['attribute' => 'new nickname']) + ]); + + // Too long nickname + $this->post('/user/profile', [ + 'action' => 'nickname', + 'new_nickname' => str_random(256) + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.max.string', ['attribute' => 'new nickname', 'max' => 255]) + ]); + + // Change nickname successfully + $this->expectsEvents(Events\UserProfileUpdated::class); + $this->post('/user/profile', [ + 'action' => 'nickname', + 'new_nickname' => 'nickname' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 0, + 'msg' => trans('user.profile.nickname.success', ['nickname' => 'nickname']) + ]); + $this->assertEquals('nickname', User::find($user->uid)->nickname); + + // Change password without `current_password` field + $this->post('/user/profile', [ + 'action' => 'password' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.required', ['attribute' => 'current password']) + ]); + + // Too short current password + $this->post('/user/profile', [ + 'action' => 'password', + 'current_password' => '1' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.min.string', ['attribute' => 'current password', 'min' => 6]) + ]); + + // Too long current password + $this->post('/user/profile', [ + 'action' => 'password', + 'current_password' => str_random(17) + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.max.string', ['attribute' => 'current password', 'max' => 16]) + ]); + + // Too short new password + $this->post('/user/profile', [ + 'action' => 'password', + 'current_password' => '12345678', + 'new_password' => '1' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.min.string', ['attribute' => 'new password', 'min' => 8]) + ]); + + // Too long new password + $this->post('/user/profile', [ + 'action' => 'password', + 'current_password' => '12345678', + 'new_password' => str_random(17) + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.max.string', ['attribute' => 'new password', 'max' => 16]) + ]); + + // Wrong old password + $this->post('/user/profile', [ + 'action' => 'password', + 'current_password' => '1234567', + 'new_password' => '87654321' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('user.profile.password.wrong-password') + ]); + + // Change password successfully + $this->expectsEvents(Events\EncryptUserPassword::class); + $this->post('/user/profile', [ + 'action' => 'password', + 'current_password' => '12345678', + 'new_password' => '87654321' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 0, + 'msg' => trans('user.profile.password.success') + ]); + $this->assertTrue(User::find($user->uid)->verifyPassword('87654321')); + // After changed password, user should re-login. + $this->visit('/user')->seePageIs('/auth/login'); + + $user = User::find($user->uid); + // Change email without `new_email` field + $this->actAs($user) + ->post( + '/user/profile', + ['action' => 'email'], + ['X-Requested-With' => 'XMLHttpRequest']) + ->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.required', ['attribute' => 'new email']) + ]); + + // Invalid email + $this->post('/user/profile', [ + 'action' => 'email', + 'new_email' => 'not_an_email' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.email', ['attribute' => 'new email']) + ]); + + // Too short current password + $this->post('/user/profile', [ + 'action' => 'email', + 'new_email' => 'a@b.c', + 'password' => '1' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.min.string', ['attribute' => 'password', 'min' => 6]) + ]); + + // Too long current password + $this->post('/user/profile', [ + 'action' => 'email', + 'new_email' => 'a@b.c', + 'password' => str_random(17) + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.max.string', ['attribute' => 'password', 'max' => 16]) + ]); + + // Use a duplicated email + $this->post('/user/profile', [ + 'action' => 'email', + 'new_email' => $user->email, + 'password' => '87654321' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('user.profile.email.existed') + ]); + + // Wrong password + $this->post('/user/profile', [ + 'action' => 'email', + 'new_email' => 'a@b.c', + 'password' => '7654321' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('user.profile.email.wrong-password') + ]); + + // Change email successfully + $this->post('/user/profile', [ + 'action' => 'email', + 'new_email' => 'a@b.c', + 'password' => '87654321' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 0, + 'msg' => trans('user.profile.email.success') + ]); + $this->assertEquals('a@b.c', User::find($user->uid)->email); + // After changed email, user should re-login. + $this->visit('/user')->seePageIs('/auth/login'); + + $user = User::find($user->uid); + // Delete account without `password` field + $this->actAs($user) + ->post( + '/user/profile', + ['action' => 'delete'], + ['X-Requested-With' => 'XMLHttpRequest']) + ->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.required', ['attribute' => 'password']) + ]); + + // Too short current password + $this->post('/user/profile', [ + 'action' => 'delete', + 'password' => '1' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.min.string', ['attribute' => 'password', 'min' => 6]) + ]); + + // Too long current password + $this->post('/user/profile', [ + 'action' => 'delete', + 'password' => str_random(17) + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.max.string', ['attribute' => 'password', 'max' => 16]) + ]); + + // Wrong password + $this->post('/user/profile', [ + 'action' => 'delete', + 'password' => '7654321' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ])->seeJson([ + 'errno' => 1, + 'msg' => trans('user.profile.delete.wrong-password') + ]); + + // Delete account successfully + $this->post('/user/profile', [ + 'action' => 'delete', + 'password' => '87654321' + ])->seeJson([ + 'errno' => 0, + 'msg' => trans('user.profile.delete.success') + ])->seeCookie('uid', '') + ->seeCookie('token', ''); + $this->assertNull(User::find($user->uid)); + } + + public function testSetAvatar() + { + $user = factory(User::class)->create(); + $steve = factory(\App\Models\Texture::class)->create(); + $cape = factory(\App\Models\Texture::class, 'cape')->create(); + + // Without `tid` field + $this->actAs($user) + ->post('/user/profile/avatar', [], [ + 'X-Requested-With' => 'XMLHttpRequest' + ]) + ->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.required', ['attribute' => 'tid']) + ]); + + // TID is not a integer + $this->actAs($user) + ->post('/user/profile/avatar', [ + 'tid' => 'string' + ], [ + 'X-Requested-With' => 'XMLHttpRequest' + ]) + ->seeJson([ + 'errno' => 1, + 'msg' => trans('validation.integer', ['attribute' => 'tid']) + ]); + + // Texture cannot be found + $this->actAs($user) + ->post('/user/profile/avatar', [ + 'tid' => 0 + ]) + ->seeJson([ + 'errno' => 1, + 'msg' => trans('skinlib.non-existent') + ]); + + // Use cape + $this->actAs($user) + ->post('/user/profile/avatar', [ + 'tid' => $cape->tid + ]) + ->seeJson([ + 'errno' => 1, + 'msg' => trans('user.profile.avatar.wrong-type') + ]); + + // Success + $this->actAs($user) + ->post('/user/profile/avatar', [ + 'tid' => $steve->tid + ]) + ->seeJson([ + 'errno' => 0, + 'msg' => trans('user.profile.avatar.success') + ]); + $this->assertEquals($steve->tid, User::find($user->uid)->avatar); + } +}