From f36852a24bab9de7d698b9b85cd7579b489e3207 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Thu, 28 May 2020 18:44:42 +0800 Subject: [PATCH] add events and filters for closet --- app/Http/Controllers/ClosetController.php | 81 ++++++-- .../ControllersTest/ClosetControllerTest.php | 185 ++++++++++++++++-- 2 files changed, 230 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/ClosetController.php b/app/Http/Controllers/ClosetController.php index 87025916..e4984717 100644 --- a/app/Http/Controllers/ClosetController.php +++ b/app/Http/Controllers/ClosetController.php @@ -6,6 +6,8 @@ use App\Models\Texture; use App\Models\User; use Auth; use Blessing\Filter; +use Blessing\Rejection; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; @@ -64,17 +66,28 @@ class ClosetController extends Controller return auth()->user()->closet()->pluck('texture_tid'); } - public function add(Request $request) - { - $this->validate($request, [ + public function add( + Request $request, + Dispatcher $dispatcher, + Filter $filter + ) { + ['tid' => $tid, 'name' => $name] = $request->validate([ 'tid' => 'required|integer', 'name' => 'required', ]); + /** @var User */ $user = Auth::user(); + $name = $filter->apply('add_closet_item_name', $name, [$tid]); + $dispatcher->dispatch('closet.adding', [$tid, $name]); + + $can = $filter->apply('can_add_closet_item', true, [$tid, $name]); + if ($can instanceof Rejection) { + return json($can->getReason(), 1); + } if ($user->score < option('score_per_closet_item')) { - return json(trans('user.closet.add.lack-score'), 7); + return json(trans('user.closet.add.lack-score'), 1); } $tid = $request->tid; @@ -98,6 +111,8 @@ class ClosetController extends Controller $texture->likes++; $texture->save(); + $dispatcher->dispatch('closet.added', [$texture, $name]); + $uploader = User::find($texture->uploader); if ($uploader && $uploader->uid != $user->uid) { $uploader->score += option('score_award_per_like', 0); @@ -107,26 +122,52 @@ class ClosetController extends Controller return json(trans('user.closet.add.success', ['name' => $request->input('name')]), 0); } - public function rename(Request $request, $tid) - { - $this->validate($request, ['name' => 'required']); + public function rename( + Request $request, + Dispatcher $dispatcher, + Filter $filter, + $tid + ) { + ['name' => $name] = $request->validate(['name' => 'required']); + /** @var User */ $user = auth()->user(); - if ($user->closet()->where('tid', $request->tid)->count() == 0) { + $name = $filter->apply('rename_closet_item_name', $name, [$tid]); + $dispatcher->dispatch('closet.renaming', [$tid, $name]); + + $item = $user->closet()->find($tid); + if (empty($item)) { + return json(trans('user.closet.remove.non-existent'), 1); + } + $previousName = $item->pivot->item_name; + + $can = $filter->apply('can_rename_closet_item', true, [$item, $name]); + if ($can instanceof Rejection) { + return json($can->getReason(), 1); + } + + $user->closet()->updateExistingPivot($tid, ['item_name' => $name]); + + $dispatcher->dispatch('closet.renamed', [$item, $previousName]); + + return json(trans('user.closet.rename.success', ['name' => $name]), 0); + } + + public function remove(Dispatcher $dispatcher, Filter $filter, $tid) + { + /** @var User */ + $user = auth()->user(); + + $dispatcher->dispatch('closet.removing', [$tid]); + + $item = $user->closet()->find($tid); + if (empty($item)) { return json(trans('user.closet.remove.non-existent'), 1); } - $user->closet()->updateExistingPivot($request->tid, ['item_name' => $request->name]); - - return json(trans('user.closet.rename.success', ['name' => $request->name]), 0); - } - - public function remove($tid) - { - $user = auth()->user(); - - if ($user->closet()->where('tid', $tid)->count() == 0) { - return json(trans('user.closet.remove.non-existent'), 1); + $can = $filter->apply('can_remove_closet_item', true, [$item]); + if ($can instanceof Rejection) { + return json($can->getReason(), 1); } $user->closet()->detach($tid); @@ -140,6 +181,8 @@ class ClosetController extends Controller $texture->likes--; $texture->save(); + $dispatcher->dispatch('closet.removed', [$texture]); + $uploader = User::find($texture->uploader); $uploader->score -= option('score_award_per_like', 0); $uploader->save(); diff --git a/tests/HttpTest/ControllersTest/ClosetControllerTest.php b/tests/HttpTest/ControllersTest/ClosetControllerTest.php index 1a93d433..5690cf87 100644 --- a/tests/HttpTest/ControllersTest/ClosetControllerTest.php +++ b/tests/HttpTest/ControllersTest/ClosetControllerTest.php @@ -4,7 +4,9 @@ namespace Tests; use App\Models\Texture; use App\Models\User; +use Blessing\Rejection; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Support\Facades\Event; class ClosetControllerTest extends TestCase { @@ -78,13 +80,14 @@ class ClosetControllerTest extends TestCase public function testAdd() { + Event::fake(); $uploader = factory(User::class)->create(['score' => 0]); $texture = factory(Texture::class)->create(['uploader' => $uploader->uid]); $likes = $texture->likes; $name = 'my'; option(['score_per_closet_item' => 10]); - // Missing `tid` field + // missing `tid` field $this->postJson('/user/closet/add')->assertJsonValidationErrors('tid'); // `tid` is not a integer @@ -93,24 +96,60 @@ class ClosetControllerTest extends TestCase ['tid' => 'string'] )->assertJsonValidationErrors('tid'); - // Missing `name` field + // missing `name` field $this->postJson( '/user/closet/add', ['tid' => 0] )->assertJsonValidationErrors('name'); - // The user doesn't have enough score to add a texture + // rejection + $filter = Fakes\Filter::fake(); + $filter->add( + 'can_add_closet_item', + function ($can, $tid, $itemName) use ($name, $texture) { + $this->assertEquals($name, $itemName); + $this->assertEquals($texture->tid, $tid); + + return new Rejection('rejected'); + } + ); + $this->postJson( + '/user/closet/add', + ['tid' => $texture->tid, 'name' => $name] + )->assertJson(['code' => 1, 'message' => 'rejected']); + $filter->assertApplied( + 'add_closet_item_name', + function ($itemName, $tid) use ($name, $texture) { + $this->assertEquals($name, $itemName); + $this->assertEquals($texture->tid, $tid); + + return true; + } + ); + Event::assertDispatched( + 'closet.adding', + function ($eventName, $payload) use ($name, $texture) { + $this->assertEquals($texture->tid, $payload[0]); + $this->assertEquals($name, $payload[1]); + + return true; + } + ); + Event::assertNotDispatched('closet.added'); + Fakes\Filter::fake(); + + // the user doesn't have enough score to add a texture $this->user->score = 0; $this->user->save(); $this->postJson( '/user/closet/add', ['tid' => $texture->tid, 'name' => $name] )->assertJson([ - 'code' => 7, + 'code' => 1, 'message' => trans('user.closet.add.lack-score'), ]); - // Add a not-existed texture + // add a not-existed texture $this->user->score = 100; $this->user->save(); $this->postJson( @@ -121,7 +160,7 @@ class ClosetControllerTest extends TestCase 'message' => trans('user.closet.add.not-found'), ]); - // Texture is private + // texture is private option(['score_award_per_like' => 5]); $privateTexture = factory(Texture::class)->create([ 'public' => false, @@ -135,7 +174,7 @@ class ClosetControllerTest extends TestCase 'message' => trans('skinlib.show.private'), ]); - // Administrator can add it. + // administrator can add it. $privateTexture = factory(Texture::class)->state('private')->create([ 'uploader' => 0, ]); @@ -148,7 +187,8 @@ class ClosetControllerTest extends TestCase 'message' => trans('user.closet.add.success', ['name' => $name]), ]); - // Add a texture successfully + // add a texture successfully + Event::fake(); $this->actingAs($this->user) ->postJson( '/user/closet/add', @@ -163,8 +203,26 @@ class ClosetControllerTest extends TestCase $this->assertEquals(1, $this->user->closet()->count()); $uploader->refresh(); $this->assertEquals(5, $uploader->score); + Event::assertDispatched( + 'closet.adding', + function ($eventName, $payload) use ($name, $texture) { + $this->assertEquals($texture->tid, $payload[0]); + $this->assertEquals($name, $payload[1]); - // If the texture is duplicated, should be warned + return true; + } + ); + Event::assertDispatched( + 'closet.added', + function ($eventName, $payload) use ($name, $texture) { + $this->assertTrue($texture->is($payload[0])); + $this->assertEquals($name, $payload[1]); + + return true; + } + ); + + // if the texture is duplicated, should be warned $this->postJson( '/user/closet/add', ['tid' => $texture->tid, 'name' => $name] @@ -176,43 +234,120 @@ class ClosetControllerTest extends TestCase public function testRename() { + Event::fake(); $texture = factory(Texture::class)->create(); $name = 'new'; - // Missing `name` field + // missing `name` field $this->postJson('/user/closet/rename/0')->assertJsonValidationErrors('name'); - // Rename a not-existed texture + // rejection + $filter = Fakes\Filter::fake(); + $filter->add( + 'can_rename_closet_item', + function ($can, $item, $itemName) use ($texture, $name) { + $this->assertTrue($texture->is($item)); + $this->assertEquals($name, $itemName); + + return new Rejection('rejected'); + } + ); + $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); + $this->postJson('/user/closet/rename/'.$texture->tid, ['name' => $name]) + ->assertJson(['code' => 1, 'message' => 'rejected']); + $filter->assertApplied( + 'rename_closet_item_name', + function ($itemName, $tid) use ($name, $texture) { + $this->assertEquals($name, $itemName); + $this->assertEquals($texture->tid, $tid); + + return true; + } + ); + Event::assertDispatched( + 'closet.renaming', + function ($eventName, $payload) use ($name, $texture) { + $this->assertEquals($texture->tid, $payload[0]); + $this->assertEquals($name, $payload[1]); + + return true; + } + ); + Event::assertNotDispatched('closet.renamed'); + $this->user->closet()->detach($texture->tid); + + // rename a not-existed texture + Fakes\Filter::fake(); $this->postJson('/user/closet/rename/-1', ['name' => $name]) ->assertJson([ 'code' => 1, 'message' => trans('user.closet.remove.non-existent'), ]); - // Rename a closet item successfully - $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); + // rename a closet item successfully + Event::fake(); + $this->user->closet()->attach($texture->tid, ['item_name' => $texture->name]); $this->postJson('/user/closet/rename/'.$texture->tid, ['name' => $name]) ->assertJson([ 'code' => 0, 'message' => trans('user.closet.rename.success', ['name' => $name]), ]); $this->assertEquals(1, $this->user->closet()->where('item_name', $name)->count()); + Event::assertDispatched( + 'closet.renaming', + function ($eventName, $payload) use ($name, $texture) { + $this->assertEquals($texture->tid, $payload[0]); + $this->assertEquals($name, $payload[1]); + + return true; + } + ); + Event::assertDispatched( + 'closet.renamed', + function ($eventName, $payload) use ($name, $texture) { + $this->assertTrue($texture->is($payload[0])); + $this->assertEquals($texture->name, $payload[1]); + + return true; + } + ); } public function testRemove() { + Event::fake(); $uploader = factory(User::class)->create(['score' => 5]); $texture = factory(Texture::class)->create(['uploader' => $uploader->uid]); $likes = $texture->likes; - // Rename a not-existed texture + // rename a not-existed texture $this->postJson('/user/closet/remove/-1') ->assertJson([ 'code' => 1, 'message' => trans('user.closet.remove.non-existent'), ]); + Event::assertDispatched('closet.removing', function ($eventName, $payload) { + $this->assertEquals(-1, $payload[0]); - // Should return score if `return_score` is true + return true; + }); + Event::assertNotDispatched('closet.removed'); + + // rejection + $filter = Fakes\Filter::fake(); + $filter->add('can_remove_closet_item', function ($can, $item) use ($texture) { + $this->assertTrue($texture->is($item)); + + return new Rejection('rejected'); + }); + $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); + $this->postJson('/user/closet/remove/'.$texture->tid) + ->assertJson(['code' => 1, 'message' => 'rejected']); + $this->user->closet()->detach($texture->tid); + Fakes\Filter::fake(); + + // should return score if `return_score` is true + Event::fake(); option(['score_award_per_like' => 5]); $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $score = $this->user->score; @@ -221,15 +356,31 @@ class ClosetControllerTest extends TestCase 'code' => 0, 'message' => trans('user.closet.remove.success'), ]); - $this->assertEquals($likes - 1, Texture::find($texture->tid)->likes); + $this->assertEquals($likes - 1, $texture->fresh()->likes); $this->assertEquals($score + option('score_per_closet_item'), $this->user->score); $this->assertEquals(0, $this->user->closet()->count()); $uploader->refresh(); $this->assertEquals(0, $uploader->score); + Event::assertDispatched( + 'closet.removing', + function ($eventName, $payload) use ($texture) { + $this->assertEquals($texture->tid, $payload[0]); + + return true; + } + ); + Event::assertDispatched( + 'closet.removed', + function ($eventName, $payload) use ($texture) { + $this->assertTrue($texture->is($payload[0])); + + return true; + } + ); $texture = Texture::find($texture->tid); $likes = $texture->likes; - // Should not return score if `return_score` is false + // should not return score if `return_score` is false option(['return_score' => false]); $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $score = $this->user->score;