90.人机验证(二)
- 本系列文章为
laracasts.com
的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频教程第 90 小节:Recaptcha Refactoring
本节内容
上一节我们引入了人机验证机制,本节我们来补上测试,并做些重构。首先我们增加人机验证的测试:
forum\tests\Feature\CreateThreadsTest.php
.
.
/** @test */
public function a_thread_requires_a_body()
{
$this->publishThread(['body' => null])
->assertSessionHasErrors('body');
}
/** @test */
public function a_thread_requires_recaptcha_verification()
{
$this->publishThread(['g-recaptcha-response' => 'test'])
->assertSessionHasErrors('g-recaptcha-response');
}
.
.
然后我们将人机验证的逻辑封装成一个单独的验证规则。新建验证规则:
$ php artisan make:rule Recaptcha
抽取验证逻辑:
forum\app\Rules\Recaptcha.php
.
.
public function passes($attribute, $value)
{
$response = Zttp::asFormParams()->post('https://www.google.com/recaptcha/api/siteverify',[
'secret' => config('services.recaptcha.secret'),
'response' => $value,
'remoteip' => request()->ip()
]);
return $response->json()['success'];
}
public function message()
{
return 'The recaptcha verification failed.Try again.';
}
}
应用验证规则:
forum\app\Http\Controllers\ThreadsController.php
.
.
public function store(Request $request,Recaptcha $recaptch)
{
$this->validate($request,[
'title' => 'required|spamfree',
'body' => 'required|spamfree',
'channel_id' => 'required|exists:channels,id',
'g-recaptcha-response' => ['required',$recaptch]
]);
$thread = Thread::create([
'user_id' => auth()->id(),
'channel_id' => request('channel_id'),
'title' => request('title'),
'body' => request('body')
]);
if (request()->wantsJson()) {
return response($thread,201);
}
return redirect($thread->path())
->with('flash','Your thread has been published!');
}
.
.
注:头部引入
use App\Rules\Recaptcha;
运行测试:
我们在创建话题时添加了新的验证规则,在CreateThreadsTest.php
测试文件下的多个测试会报错,因为我们没有传入正确的token
值。我们不需要在每个测试中都发送 Http 请求获取到正确的token
值,所以我们来进行处理,只有在我们需要对 Http 请求进行测试时,我们才发送请求进行验证。
forum\tests\Feature\CreateThreadsTest.php
<?php
namespace Tests\Feature;
use App\Activity;
use App\Rules\Recaptcha;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class CreateThreadsTest extends TestCase
{
use DatabaseMigrations;
public function setUp()
{
parent::setUp();
// 默认情况下,Recaptcha 验证规则都会通过
app()->singleton(Recaptcha::class,function() {
return \Mockery::mock(Recaptcha::class,function ($m) {
$m->shouldReceive('passes')->andReturn(true);
});
});
}
.
.
/** @test */
public function a_user_can_create_new_forum_threads()
{
$this->signIn();
$thread = make('App\Thread');
// 添加 g-recaptcha-response 即可通过验证
$response = $this->post(route('threads'),$thread->toArray() + ['g-recaptcha-response' => 'token']);
$this->get($response->headers->get('Location'))
->assertSee($thread->title)
->assertSee($thread->body);
}
.
.
/** @test */
public function a_thread_requires_recaptcha_verification()
{
unset(app()[Recaptcha::class]);
// 添加 g-recaptcha-response 即可通过验证
$this->publishThread(['g-recaptcha-response' => 'test'])
->assertSessionHasErrors('g-recaptcha-response');
}
.
.
/** @test */
public function a_thread_requires_a_unique_slug()
{
$this->signIn();
create('App\Thread',[],2);
$thread = create('App\Thread',['title' => 'Foo Title']);
$this->assertEquals($thread->fresh()->slug,'foo-title');
// 添加 g-recaptcha-response 即可通过验证
$thread = $this->postJson(route('threads'),$thread->toArray() + ['g-recaptcha-response' => 'token'])->json();
$this->assertEquals("foo-title-{$thread['id']}",$thread['slug']);
}
/** @test */
public function a_thread_with_a_title_that_ends_in_a_number_should_generate_the_proper_slug()
{
$this->signIn();
$thread = create('App\Thread',['title' => 'Something 24']);
// 添加 g-recaptcha-response 即可通过验证
$thread = $this->postJson(route('threads'),$thread->toArray() + ['g-recaptcha-response' => 'token'])->json();
$this->assertEquals("something-24-{$thread['id']}",$thread['slug']);
}
.
.
}
运行整个文件下的测试:
运行全部测试: