73.邮箱认证(三)
- 本系列文章为
laracasts.com
的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频教程第 73 小节:Users Must Confirm Their Email Address: #3 - Cleanup
本节内容
上一节我们利用事件机制实现了注册用户时发送认证邮件的功能,但是只为了发送邮件就定义了事件,似乎有点杀鸡用牛刀。所以本节我们通过重写registered()
方法很轻松地实现相同的功能:
forum\app\Http\Controllers\Auth\RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Mail\PleaseConfirmYourEmail;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
use RegistersUsers;
protected $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest');
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
protected function create(array $data)
{
return User::forceCreate([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'confirmation_token' => str_random(25)
]);
}
protected function registered(Request $request, $user)
{
Mail::to($user)->send(new PleaseConfirmYourEmail($user));
return redirect($this->redirectPath());
}
}
然后我们可以去掉事件的注册:
forum\app\Providers\EventServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
'App\Events\ThreadReceivedNewReply' => [
'App\Listeners\NotifyMentionedUsers',
'App\Listeners\NotifySubscribers',
]
];
.
.
}
接着修改我们的测试:
tests\Feature\RegisrationTest.php
.
.
/** @test */
public function a_confirmation_email_is_sent_upon_registration()
{
Mail::fake();
// 用路由命名代替 url
$this->post(route('register'),[
'name' => 'NoNo1',
'email' => 'NoNo1@example.com',
'password' => '123456',
'password_confirmation' => '123456'
]);
Mail::assertSent(PleaseConfirmYourEmail::class);
}
.
.
运行测试:
接下来我们来进行些重构,用路由命名代替url
:
forum\tests\Feature\RegistrationTest.php
<?php
namespace Tests\Feature;
use App\Mail\PleaseConfirmYourEmail;
use App\User;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class RegistrationTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function a_confirmation_email_is_sent_upon_registration()
{
Mail::fake();
// 用路由命名代替 url
$this->post(route('register'),[
'name' => 'NoNo1',
'email' => 'NoNo1@example.com',
'password' => '123456',
'password_confirmation' => '123456'
]);
Mail::assertSent(PleaseConfirmYourEmail::class);
}
/** @test */
public function user_can_fully_confirm_their_email_addresses()
{
// 用路由命名代替 url
$this->post(route('register'),[
'name' => 'NoNo1',
'email' => 'NoNo1@example.com',
'password' => '123456',
'password_confirmation' => '123456'
]);
$user = User::whereName('NoNo1')->first();
$this->assertFalse($user->confirmed);
$this->assertNotNull($user->confirmation_token);
// 用路由命名代替 url
$this->get(route('register.confirm',['token' => $user->confirmation_token]))
->assertRedirect(route('threads'));
$this->assertTrue($user->fresh()->confirmed);
}
}
我们来为路由加上命名:
forum\routes\web.php
.
.
Route::get('threads','ThreadsController@index')->name('threads');
.
.
Route::get('/register/confirm','Api\RegisterConfirmationController@index')->name('register.confirm');
.
.
我们运行测试:
现在我们来运行全部测试:
以上的报错原因是因为我们默认一个新用户是未认证的,而我们对未认证用户做了限制发布话题的设置。对于我们的测试而言,我们大多数的功能测试的前提都是在用户已认证的情况下的,所以我们来对模型工厂文件进行一点修改:
forum\database\factories\ModelFactory.php
<?php
$factory->define(App\User::class, function (Faker\Generator $faker) {
static $password;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('123456'),
'remember_token' => str_random(10),
'confirmed' => true,
];
});
$factory->state(App\User::class,'unconfirmed',function () {
return [
'confirmed' => false
];
});
.
.
我们默认新用户是已认证状态,当我们需要测试未认证的相关逻辑时,我们调用unconfirmed
即可。接下来我们对CreateThreadsTest.php
进行清理,因改动处较多,说明放在了代码的注释中:
<?php
namespace Tests\Feature;
use App\Activity;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class CreateThreadsTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function guests_may_not_create_threads()
{
$this->withExceptionHandling();
$this->get('/threads/create')
->assertRedirect(route('login')); // 应用路由命名
$this->post(route('threads')) // 应用路由命名
->assertRedirect(route('login')); // 应用路由命名
}
// 修改测试命名,更加辨识度
/** @test */
public function new_users_must_first_confirm_their_email_address_before_creating_threads()
{
// 调用 unconfirmed,生成未认证用户
$user = factory('App\User')->states('unconfirmed')->create();
$this->signIn($user);
$thread = make('App\Thread');
$this->post(route('threads'),$thread->toArray())
->assertRedirect('/threads')
->assertSessionHas('flash','You must first confirm your email address.');
}
// 修改测试命名,更加辨识度
/** @test */
public function a_user_can_create_new_forum_threads()
{
$this->signIn();
$thread = make('App\Thread');
$response = $this->post(route('threads'),$thread->toArray());// 应用路由命名
$this->get($response->headers->get('Location'))
->assertSee($thread->title)
->assertSee($thread->body);
}
/** @test */
public function a_thread_requires_a_title()
{
$this->publishThread(['title' => null])
->assertSessionHasErrors('title');
}
/** @test */
public function a_thread_requires_a_body()
{
$this->publishThread(['body' => null])
->assertSessionHasErrors('body');
}
/** @test */
public function a_thread_requires_a_valid_channel()
{
factory('App\Channel',2)->create(); // 新建两个 Channel,id 分别为 1 跟 2
$this->publishThread(['channel_id' => null])
->assertSessionHasErrors('channel_id');
$this->publishThread(['channel_id' => 999]) // channle_id 为 999,是一个不存在的 Channel
->assertSessionHasErrors('channel_id');
}
/** @test */
public function unauthorized_users_may_not_delete_threads()
{
$this->withExceptionHandling();
$thread = create('App\Thread');
$this->delete($thread->path())->assertRedirect(route('login')); // 应用路由命名
$this->signIn();
$this->delete($thread->path())->assertStatus(403);
}
/** @test */
public function authorized_users_can_delete_threads()
{
$this->signIn();
$thread = create('App\Thread',['user_id' => auth()->id()]);
$reply = create('App\Reply',['thread_id' => $thread->id]);
$response = $this->json('DELETE',$thread->path());
$response->assertStatus(204);
$this->assertDatabaseMissing('threads',['id' => $thread->id]);
$this->assertDatabaseMissing('replies',['id' => $reply->id]);
$this->assertEquals(0,Activity::count());
}
public function publishThread($overrides = [])
{
$this->withExceptionHandling()->signIn();
$thread = make('App\Thread',$overrides);
return $this->post(route('threads'),$thread->toArray()); // 应用路由命名
}
}
运行测试:
接下来我们新增一个测试:
forum\tests\Feature\RegistrationTest.php
.
.
/** @test */
public function user_can_fully_confirm_their_email_addresses()
{
// 应用 【邮件模拟】
Mail::fake();
$this->post(route('register'),[
'name' => 'NoNo1',
'email' => 'NoNo1@example.com',
'password' => '123456',
'password_confirmation' => '123456'
]);
$user = User::whereName('NoNo1')->first();
$this->assertFalse($user->confirmed);
$this->assertNotNull($user->confirmation_token);
$this->get(route('register.confirm',['token' => $user->confirmation_token]))
->assertRedirect(route('threads'));
$this->assertTrue($user->fresh()->confirmed);
}
/** @test */
public function confirming_an_invalid_token()
{
// 测试无效 Token
$this->get(route('register.confirm'),['token' => 'invalid'])
->assertRedirect(route('threads'))
->assertSessionHas('flash','Unknown token.');
}
}
为了让测试通过,我们要修改控制器:
forum\app\Http\Controllers\Api\RegisterConfirmationController.php
.
.
public function index()
{
try {
User::where('confirmation_token',request('token'))
->firstOrFail()
->confirm();
}catch (\Exception $e) {
return redirect(route('threads'))
->with('flash','Unknown token.');
}
return redirect(route('threads'))
->with('flash','Your account is now confirmed! You may post to the forum.');
}
.
.
现在我们运行测试:
运行全部测试: