diff --git a/routes/api.php b/routes/api.php index 2abec02b6..1d93040ff 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,11 +1,17 @@ resolveCallback = $resolveCallback; diff --git a/src/Http/Controllers/GlobalSearchController.php b/src/Http/Controllers/GlobalSearchController.php new file mode 100644 index 000000000..b1591bdae --- /dev/null +++ b/src/Http/Controllers/GlobalSearchController.php @@ -0,0 +1,19 @@ +get(); + + return $this->response()->data($results); + } +} diff --git a/src/Http/Requests/GlobalSearchRequest.php b/src/Http/Requests/GlobalSearchRequest.php new file mode 100644 index 000000000..d35e9769a --- /dev/null +++ b/src/Http/Requests/GlobalSearchRequest.php @@ -0,0 +1,7 @@ +{static::$title}; + } + + /** + * Get the search result subtitle for the repository. + * + * @return string|null + */ + public function subtitle() + { + // + } + /** * Get a fresh instance of the model represented by the resource. * @@ -435,10 +492,8 @@ public function index(RestifyRequest $request) * Apply all of the query: search, match, sort, related. * @var AbstractPaginator $paginator */ - $paginator = RepositorySearchService::instance()->search($request, $this)->tap(function ($query) use ($request) { - // Call the local definition of the query - static::indexQuery($request, $query); - })->paginate($request->perPage ?? (static::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE)); + $paginator = RepositorySearchService::instance()->search($request, $this) + ->paginate($request->perPage ?? (static::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE)); $items = $paginator->getCollection()->map(function ($value) { return static::resolveWith($value); diff --git a/src/Restify.php b/src/Restify.php index d603f5c52..b1f089609 100644 --- a/src/Restify.php +++ b/src/Restify.php @@ -4,6 +4,7 @@ use Binaryk\LaravelRestify\Events\RestifyBeforeEach; use Binaryk\LaravelRestify\Events\RestifyStarting; +use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; use Binaryk\LaravelRestify\Traits\AuthorizesRequests; use Illuminate\Database\Eloquent\Model; @@ -15,6 +16,7 @@ class Restify { use AuthorizesRequests; + /** * The registered repository names. * @@ -39,7 +41,7 @@ class Restify /** * Get the repository class name for a given key. * - * @param string $key + * @param string $key * @return string */ public static function repositoryForKey($key) @@ -52,7 +54,7 @@ public static function repositoryForKey($key) /** * Get the repository class name for a given key. * - * @param string $model + * @param string $model * @return string */ public static function repositoryForModel($model) @@ -69,7 +71,7 @@ public static function repositoryForModel($model) /** * Register the given repositories. * - * @param array $repositories + * @param array $repositories * @return static */ public static function repositories(array $repositories) @@ -84,7 +86,7 @@ public static function repositories(array $repositories) /** * Register all of the repository classes in the given directory. * - * @param string $directory + * @param string $directory * @return void * @throws \ReflectionException */ @@ -114,7 +116,7 @@ public static function repositoriesFrom($directory) /** * Get the URI path prefix utilized by Restify. * - * @param null $plus + * @param null $plus * @return string */ public static function path($plus = null) @@ -131,7 +133,7 @@ public static function path($plus = null) * * This listener is added in the RestifyApplicationServiceProvider * - * @param \Closure|string $callback + * @param \Closure|string $callback * @return void */ public static function starting($callback) @@ -140,7 +142,7 @@ public static function starting($callback) } /** - * @param \Closure|string $callback + * @param \Closure|string $callback */ public static function beforeEach($callback) { @@ -150,10 +152,26 @@ public static function beforeEach($callback) /** * Set the callback used for intercepting any request exception. * - * @param \Closure|string $callback + * @param \Closure|string $callback */ public static function exceptionHandler($callback) { static::$renderCallback = $callback; } + + public static function globallySearchableRepositories(RestifyRequest $request) + { + return collect(static::$repositories) + ->filter(fn ($repository) => $repository::authorizedToUseRepository($request)) + ->filter(fn ($repository) => $repository::$globallySearchable) + ->sortBy(static::sortResourcesWith()) + ->all(); + } + + public static function sortResourcesWith() + { + return function ($resource) { + return $resource::label(); + }; + } } diff --git a/src/Services/Search/GlobalSearch.php b/src/Services/Search/GlobalSearch.php new file mode 100644 index 000000000..182e0312c --- /dev/null +++ b/src/Services/Search/GlobalSearch.php @@ -0,0 +1,82 @@ +request = $request; + $this->repositories = $repositories; + } + + /** + * Get the matching repositories. + * + * @return array + */ + public function get() + { + $formatted = []; + + foreach ($this->getSearchResults() as $repository => $models) { + foreach ($models as $model) { + $instance = $repository::resolveWith($model); + + $formatted[] = [ + 'repositoryName' => $repository::uriKey(), + 'repositoryTitle' => $repository::label(), + 'title' => $instance->title(), + 'subTitle' => $instance->subtitle(), + 'repositoryId' => $model->getKey(), + ]; + } + } + + return $formatted; + } + + /** + * Get the search results for the repositories. + * + * @return array + */ + protected function getSearchResults() + { + $results = []; + + foreach ($this->repositories as $repository) { + $query = RepositorySearchService::instance()->search($this->request, $repository::resolveWith($repository::newModel())); + + if (count($models = $query->limit($repository::$globalSearchResults)->get()) > 0) { + $results[$repository] = $models; + } + } + + return collect($results)->sortKeys()->all(); + } +} diff --git a/src/Services/Search/RepositorySearchService.php b/src/Services/Search/RepositorySearchService.php index b69deb0ff..1cbf6dbf1 100644 --- a/src/Services/Search/RepositorySearchService.php +++ b/src/Services/Search/RepositorySearchService.php @@ -17,7 +17,7 @@ public function search(RestifyRequest $request, Repository $repository) $query = $this->prepareMatchFields($request, $this->prepareSearchFields($request, $repository::query(), $this->fixedInput), $this->fixedInput); - return $this->prepareRelations($request, $this->prepareOrders($request, $query), $this->fixedInput); + return tap($this->prepareRelations($request, $this->prepareOrders($request, $query), $this->fixedInput), $this->applyIndexQuery($request, $repository)); } public function prepareMatchFields(RestifyRequest $request, $query, $extra = []) @@ -113,7 +113,7 @@ public function prepareSearchFields(RestifyRequest $request, $query, $extra = [] $canSearchPrimaryKey = is_numeric($search) && in_array($query->getModel()->getKeyType(), ['int', 'integer']) && ($connectionType != 'pgsql' || $search <= PHP_INT_MAX) && - in_array($query->getModel()->getKeyName(), $model::getSearchableFields()); + in_array($query->getModel()->getKeyName(), $this->repository->getSearchableFields()); if ($canSearchPrimaryKey) { $query->orWhere($query->getModel()->getQualifiedKeyName(), $search); @@ -170,4 +170,9 @@ public function setOrder($query, $param) return $query; } + + protected function applyIndexQuery(RestifyRequest $request, Repository $repository) + { + return fn ($query) => $repository::indexQuery($request, $query); + } } diff --git a/tests/Controllers/GlobalSearchControllerTest.php b/tests/Controllers/GlobalSearchControllerTest.php new file mode 100644 index 000000000..a64be8eb3 --- /dev/null +++ b/tests/Controllers/GlobalSearchControllerTest.php @@ -0,0 +1,67 @@ +create(['title' => 'First post']); + factory(Post::class)->create(['title' => 'Second post']); + factory(User::class)->create(['name' => 'First user']); + factory(User::class)->create(['name' => 'Second user']); + + $response = $this + ->withoutExceptionHandling() + ->getJson('/restify-api/search?search=Second'); + + $this->assertCount(2, $response->json('data')); + $this->assertEquals('users', $response->json('data.1.repositoryName')); + $this->assertEquals('Second post', $response->json('data.0.title')); + } + + public function test_global_search_filter_out_unauthorized_repositories() + { + Gate::policy(Post::class, PostPolicy::class); + + $_SERVER['restify.post.allowRestify'] = false; + + factory(Post::class)->create(); + factory(User::class)->create(); + + $response = $this + ->withoutExceptionHandling() + ->getJson('/restify-api/search?search=1'); + + $this->assertCount(1, $response->json('data')); + + $_SERVER['restify.post.allowRestify'] = null; + } + + public function test_global_search_filter_will_filter_with_index_query() + { + $_SERVER['restify.post.indexQueryCallback'] = function ($query) { + $query->where('id', 2); + }; + + factory(Post::class)->create(['title' => 'First post']); + factory(User::class)->create(['name' => 'First user']); + + $response = $this + ->withoutExceptionHandling() + ->getJson('/restify-api/search?search=1'); + + $this->assertCount(1, $response->json('data')); + + $_SERVER['restify.post.indexQueryCallback'] = null; + } +} diff --git a/tests/Controllers/IndexControllerTest.php b/tests/Controllers/IndexControllerTest.php deleted file mode 100644 index 1bd3835f9..000000000 --- a/tests/Controllers/IndexControllerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - */ -class IndexControllerTest extends IntegrationTest -{ - public function test_list_repository() - { - factory(User::class)->create(); - factory(User::class)->create(); - factory(User::class)->create(); - - $response = $this->withExceptionHandling() - ->getJson('/restify-api/users'); - - $response->assertJsonCount(3, 'data'); - } - - public function test_the_rest_controller_can_paginate() - { - $this->mockUsers(20); - - $class = (new class extends RestController { - public function users() - { - return $this->response($this->search(User::class)); - } - }); - - $response = $class->search(User::class, [ - 'match' => [ - 'id' => 1, - ], - ]); - $this->assertIsArray($class->search(User::class)); - $this->assertCount(1, $response['data']); - $this->assertEquals(count($class->users()->getData()->data), User::$defaultPerPage); - } -} diff --git a/tests/Controllers/RepositoryDestroyControllerTest.php b/tests/Controllers/RepositoryDestroyControllerTest.php index f229dab53..aa74711d7 100644 --- a/tests/Controllers/RepositoryDestroyControllerTest.php +++ b/tests/Controllers/RepositoryDestroyControllerTest.php @@ -3,8 +3,8 @@ namespace Binaryk\LaravelRestify\Tests\Controllers; use Binaryk\LaravelRestify\Exceptions\RestifyHandler; -use Binaryk\LaravelRestify\Tests\Fixtures\Post; -use Binaryk\LaravelRestify\Tests\Fixtures\PostPolicy; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostPolicy; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Facades\Gate; diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 95839f70c..442dec760 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -3,13 +3,8 @@ namespace Binaryk\LaravelRestify\Tests\Controllers; use Binaryk\LaravelRestify\Contracts\RestifySearchable; -use Binaryk\LaravelRestify\Fields\Field; -use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Mergeable; -use Binaryk\LaravelRestify\Repositories\Repository; -use Binaryk\LaravelRestify\Restify; -use Binaryk\LaravelRestify\Tests\Fixtures\Apple; -use Binaryk\LaravelRestify\Tests\Fixtures\AppleRepository; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -19,62 +14,60 @@ class RepositoryIndexControllerTest extends IntegrationTest public function test_repository_per_page() { - factory(Apple::class, 20)->create(); + factory(Post::class, 20)->create(); - AppleRepository::$defaultPerPage = 5; + PostRepository::$defaultPerPage = 5; - $response = $this->getJson('restify-api/apples') - ->assertStatus(200); + $response = $this->getJson('restify-api/posts'); $this->assertCount(5, $response->json('data')); - $response = $this->getJson('restify-api/apples?perPage=10'); + $response = $this->getJson('restify-api/posts?perPage=10'); $this->assertCount(10, $response->json('data')); } public function test_repository_search_query_works() { - factory(Apple::class)->create([ - 'title' => 'Some title', + factory(Post::class)->create([ + 'title' => 'Title with code word', ]); - factory(Apple::class)->create([ - 'title' => 'Another one', + factory(Post::class)->create([ + 'title' => 'Another title with code inner', ]); - factory(Apple::class)->create([ - 'title' => 'foo another', + factory(Post::class)->create([ + 'title' => 'A title with no key word', ]); - factory(Apple::class)->create([ - 'title' => 'Third apple', + factory(Post::class)->create([ + 'title' => 'Lorem ipsum dolor', ]); - AppleRepository::$search = ['title']; + PostRepository::$search = ['title']; - $response = $this->getJson('restify-api/apples?search=another') - ->assertStatus(200); + $response = $this->getJson('restify-api/posts?search=code'); $this->assertCount(2, $response->json('data')); } public function test_repository_filter_works() { - AppleRepository::$match = [ + PostRepository::$match = [ 'title' => RestifySearchable::MATCH_TEXT, ]; - factory(Apple::class)->create([ + factory(Post::class)->create([ 'title' => 'Some title', ]); - factory(Apple::class)->create([ + factory(Post::class)->create([ 'title' => 'Another one', ]); $response = $this - ->getJson('restify-api/apples?title=Another one') + ->getJson('restify-api/posts?title=Another one') ->assertStatus(200); $this->assertCount(1, $response->json('data')); @@ -82,38 +75,38 @@ public function test_repository_filter_works() public function test_repository_order() { - AppleRepository::$sort = [ + PostRepository::$sort = [ 'title', ]; - factory(Apple::class)->create(['title' => 'aaa']); + factory(Post::class)->create(['title' => 'aaa']); - factory(Apple::class)->create(['title' => 'zzz']); + factory(Post::class)->create(['title' => 'zzz']); $response = $this - ->getJson('restify-api/apples?sort=-title') + ->getJson('restify-api/posts?sort=-title') ->assertStatus(200); $this->assertEquals('zzz', $response->json('data.0.attributes.title')); $this->assertEquals('aaa', $response->json('data.1.attributes.title')); $response = $this - ->getJson('restify-api/apples?order=-title') + ->getJson('restify-api/posts?order=-title') ->assertStatus(200); $this->assertEquals('zzz', $response->json('data.1.attributes.title')); $this->assertEquals('aaa', $response->json('data.0.attributes.title')); } - public function test_repsitory_with_relations() + public function test_repository_with_relations() { - AppleRepository::$related = ['user']; + PostRepository::$related = ['user']; $user = $this->mockUsers(1)->first(); - factory(Apple::class)->create(['user_id' => $user->id]); + factory(Post::class)->create(['user_id' => $user->id]); - $response = $this->getJson('/restify-api/apples?related=user') + $response = $this->getJson('/restify-api/posts?related=user') ->assertStatus(200); $this->assertCount(1, $response->json('data.0.relationships.user')); @@ -122,62 +115,37 @@ public function test_repsitory_with_relations() public function test_index_unmergeable_repository_containes_only_explicitly_defined_fields() { - Restify::repositories([ - AppleTitleRepository::class, - ]); - - factory(Apple::class)->create(); - - $response = $this->get('/restify-api/apples-title') - ->assertStatus(200); - - $this->assertArrayHasKey('title', $response->json('data.0.attributes')); - - $this->assertArrayNotHasKey('id', $response->json('data.0.attributes')); - $this->assertArrayNotHasKey('created_at', $response->json('data.0.attributes')); + factory(Post::class)->create(); + + $response = $this->get('/restify-api/posts') + ->assertStatus(200) + ->assertJsonStructure([ + 'data' => [[ + 'attributes' => [ + 'user_id', + 'title', + 'description', + ], + ]], + ]); + + $this->assertArrayNotHasKey('image', $response->json('data.0.attributes')); } public function test_index_mergeable_repository_containes_model_attributes_and_local_fields() { - Restify::repositories([ - AppleMergeable::class, - ]); - - factory(Apple::class)->create(); - - $response = $this->get('/restify-api/apples-title-mergeable') - ->assertStatus(200); - - $this->assertArrayHasKey('title', $response->json('data.0.attributes')); - $this->assertArrayHasKey('id', $response->json('data.0.attributes')); - $this->assertArrayHasKey('created_at', $response->json('data.0.attributes')); - } -} - -class AppleTitleRepository extends Repository -{ - public static $uriKey = 'apples-title'; - - public static $model = Apple::class; - - public function fields(RestifyRequest $request) - { - return [ - Field::make('title'), - ]; - } -} - -class AppleMergeable extends Repository implements Mergeable -{ - public static $uriKey = 'apples-title-mergeable'; - - public static $model = Apple::class; - - public function fields(RestifyRequest $request) - { - return [ - Field::make('title'), - ]; + factory(Post::class)->create(); + + $this->get('/restify-api/posts-mergeable') + ->assertJsonStructure([ + 'data' => [[ + 'attributes' => [ + 'user_id', + 'title', + 'description', + 'image', + ], + ]], + ]); } } diff --git a/tests/Controllers/RepositoryShowControllerTest.php b/tests/Controllers/RepositoryShowControllerTest.php index c6a27a044..84ad5b51a 100644 --- a/tests/Controllers/RepositoryShowControllerTest.php +++ b/tests/Controllers/RepositoryShowControllerTest.php @@ -2,13 +2,7 @@ namespace Binaryk\LaravelRestify\Tests\Controllers; -use Binaryk\LaravelRestify\Fields\Field; -use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Mergeable; -use Binaryk\LaravelRestify\Repositories\Repository; -use Binaryk\LaravelRestify\Restify; -use Binaryk\LaravelRestify\Tests\Fixtures\Apple; -use Binaryk\LaravelRestify\Tests\Fixtures\Post; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; use Binaryk\LaravelRestify\Tests\IntegrationTest; /** @@ -39,51 +33,44 @@ public function test_basic_show() public function test_show_will_authorize_fields() { - factory(Apple::class)->create(); + factory(Post::class)->create(); - Restify::repositories([ - AppleAuthorized::class, - ]); - - $_SERVER['can.see.title'] = false; - $response = $this->getJson('/restify-api/apple-authorized/1'); + $_SERVER['postAuthorize.can.see.title'] = false; + $response = $this->getJson('/restify-api/post-authorizes/1'); $this->assertArrayNotHasKey('title', $response->json('data.attributes')); - $_SERVER['can.see.title'] = true; - $response = $this->getJson('/restify-api/apple-authorized/1'); + $_SERVER['postAuthorize.can.see.title'] = true; + $response = $this->getJson('/restify-api/post-authorizes/1'); $this->assertArrayHasKey('title', $response->json('data.attributes')); } public function test_show_will_take_into_consideration_show_callback() { - factory(Apple::class)->create([ - 'title' => 'Eduard', - ]); + $_SERVER['postAuthorize.can.see.title'] = true; - Restify::repositories([ - AppleAuthorized::class, - ]); + factory(Post::class)->create(['title' => 'Eduard']); - $response = $this->getJson('/restify-api/apple-authorized/1'); + $response = $this->getJson('/restify-api/post-authorizes/1'); $this->assertSame('EDUARD', $response->json('data.attributes.title')); } public function test_show_unmergeable_repository_containes_only_explicitly_defined_fields() { - factory(Apple::class)->create([ - 'title' => 'Eduard', - ]); - - Restify::repositories([ - AppleAuthorized::class, - ]); + factory(Post::class)->create(['title' => 'Eduard']); - $response = $this->getJson('/restify-api/apple-authorized/1'); - - $this->assertArrayHasKey('title', $response->json('data.attributes')); + $response = $this->getJson('/restify-api/posts/1') + ->assertJsonStructure([ + 'data' => [ + 'attributes' => [ + 'user_id', + 'title', + 'description', + ], + ], + ]); $this->assertArrayNotHasKey('id', $response->json('data.attributes')); $this->assertArrayNotHasKey('created_at', $response->json('data.attributes')); @@ -91,38 +78,21 @@ public function test_show_unmergeable_repository_containes_only_explicitly_defin public function test_show_mergeable_repository_containes_model_attributes_and_local_fields() { - factory(Apple::class)->create([ - 'title' => 'Eduard', - ]); - - Restify::repositories([ - AppleAuthorizedMergeable::class, - ]); - - $response = $this->getJson('/restify-api/apple-authorized-mergeable/1'); + factory(Post::class)->create(['title' => 'Eduard']); - $this->assertArrayHasKey('title', $response->json('data.attributes')); - $this->assertArrayHasKey('id', $response->json('data.attributes')); - $this->assertArrayHasKey('created_at', $response->json('data.attributes')); - } -} - -class AppleAuthorized extends Repository -{ - public static $uriKey = 'apple-authorized'; - - public static $model = Apple::class; - - public function fields(RestifyRequest $request) - { - return [ - Field::make('title')->canSee(fn () => $_SERVER['can.see.title'] ?? true) - ->showCallback(fn ($value) => strtoupper($value)), - ]; + $this->getJson('/restify-api/posts-mergeable/1') + ->assertJsonStructure([ + 'data' => [ + 'attributes' => [ + 'id', + 'user_id', + 'title', + 'image', + 'description', + 'created_at', + 'updated_at', + ], + ], + ]); } } - -class AppleAuthorizedMergeable extends AppleAuthorized implements Mergeable -{ - public static $uriKey = 'apple-authorized-mergeable'; -} diff --git a/tests/Controllers/RepositoryStoreControllerTest.php b/tests/Controllers/RepositoryStoreControllerTest.php index 685c97757..d84d270bd 100644 --- a/tests/Controllers/RepositoryStoreControllerTest.php +++ b/tests/Controllers/RepositoryStoreControllerTest.php @@ -2,8 +2,8 @@ namespace Binaryk\LaravelRestify\Tests\Controllers; -use Binaryk\LaravelRestify\Tests\Fixtures\Post; -use Binaryk\LaravelRestify\Tests\Fixtures\PostPolicy; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostPolicy; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Support\Facades\Gate; @@ -100,7 +100,6 @@ public function test_will_not_store_unauthorized_fields() 'title' => 'Some post title', 'description' => 'A very short description', ]) - ->dump() ->assertStatus(201); $_SERVER['posts.description.authorized'] = false; @@ -118,7 +117,6 @@ public function test_will_not_store_readonly_fields() 'title' => 'Some post title', 'description' => 'A very short description', ]) - ->dump() ->assertStatus(201); $this->assertNull($r->json('data.attributes.image')); diff --git a/tests/Controllers/RepositoryUpdateControllerTest.php b/tests/Controllers/RepositoryUpdateControllerTest.php index 64b68bee1..af2db9316 100644 --- a/tests/Controllers/RepositoryUpdateControllerTest.php +++ b/tests/Controllers/RepositoryUpdateControllerTest.php @@ -3,14 +3,8 @@ namespace Binaryk\LaravelRestify\Tests\Controllers; use Binaryk\LaravelRestify\Exceptions\RestifyHandler; -use Binaryk\LaravelRestify\Fields\Field; -use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Mergeable; -use Binaryk\LaravelRestify\Repositories\Repository; -use Binaryk\LaravelRestify\Restify; -use Binaryk\LaravelRestify\Tests\Fixtures\Apple; -use Binaryk\LaravelRestify\Tests\Fixtures\Post; -use Binaryk\LaravelRestify\Tests\Fixtures\PostPolicy; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostPolicy; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Facades\Gate; @@ -75,13 +69,11 @@ public function test_unathorized_to_update() public function test_do_not_update_fields_without_permission() { - Restify::repositories([AppleUnauthorizedField::class]); + $post = factory(Post::class)->create(['user_id' => 1, 'title' => 'Title']); - $post = factory(Apple::class)->create(['user_id' => 1, 'title' => 'Title']); + $_SERVER['posts.authorizable.title'] = false; - $_SERVER['restify.apple.updateable'] = false; - - $response = $this->putJson('/restify-api/apple-unauthorized-put/'.$post->id, [ + $response = $this->putJson('/restify-api/post-with-unathorized-fields/'.$post->id, [ 'title' => 'Updated title', 'user_id' => 2, ])->assertStatus(200); @@ -92,23 +84,16 @@ public function test_do_not_update_fields_without_permission() public function test_update_fillable_fields_for_mergeable_repository() { - Restify::repositories([ - AppleUpdateMergeable::class, - ]); - - $apple = factory(Apple::class)->create(['user_id' => 1, 'title' => 'Title', 'color' => 'red']); + $post = factory(Post::class)->create(['user_id' => 1, 'title' => 'Title', 'image' => 'red.png']); - $response = $this->putJson('/restify-api/apple-update-extra/'.$apple->id, [ + $response = $this->putJson('/restify-api/posts-mergeable/'.$post->id, [ 'title' => 'Updated title', - 'color' => 'blue', - 'user_id' => 2, + 'image' => 'image.png', // via mergeable ]) - ->dump() ->assertStatus(200); - $this->assertEquals('Updated title', $response->json('data.attributes.title')); // via extra - $this->assertEquals('blue', $response->json('data.attributes.color')); // via extra - $this->assertEquals(2, $response->json('data.attributes.user_id')); // via field + $this->assertEquals('Updated title', $response->json('data.attributes.title')); + $this->assertEquals('image.png', $response->json('data.attributes.image')); // via extra } public function test_will_not_update_readonly_fields() @@ -123,41 +108,8 @@ public function test_will_not_update_readonly_fields() 'title' => 'Some post title', 'description' => 'A very short description', ]) - ->dump() ->assertStatus(200); $this->assertNull($r->json('data.attributes.image')); } } - -class AppleUnauthorizedField extends Repository -{ - public static $uriKey = 'apple-unauthorized-put'; - - public static $model = Apple::class; - - public function fields(RestifyRequest $request) - { - return [ - Field::make('title')->canUpdate(fn ($value) => $_SERVER['restify.apple.updateable']), - - Field::make('user_id')->canUpdate(fn ($value) => true), - ]; - } -} - -class AppleUpdateMergeable extends Repository implements Mergeable -{ - public static $uriKey = 'apple-update-extra'; - - public static $model = Apple::class; - - public function fields(RestifyRequest $request) - { - return [ - Field::make('title')->canUpdate(fn ($value) => true), - - Field::make('user_id')->canUpdate(fn ($value) => true), - ]; - } -} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 7f920570f..000000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,14 +0,0 @@ -assertTrue(true); - } -} diff --git a/tests/Factories/AppleFactory.php b/tests/Factories/AppleFactory.php deleted file mode 100644 index 8a0683d02..000000000 --- a/tests/Factories/AppleFactory.php +++ /dev/null @@ -1,21 +0,0 @@ -define(Apple::class, function (Faker $faker) { - return [ - 'title' => $faker->text(50), - ]; -}); diff --git a/tests/Factories/BookFactory.php b/tests/Factories/BookFactory.php deleted file mode 100644 index 9835c02c2..000000000 --- a/tests/Factories/BookFactory.php +++ /dev/null @@ -1,24 +0,0 @@ -define(Binaryk\LaravelRestify\Tests\Fixtures\Book::class, function (Faker $faker) { - return [ - 'title' => $faker->text(30), - 'description' => $faker->text, - 'author' => $faker->name, - 'price' => $faker->randomFloat(), - 'stock' => $faker->numberBetween(0, 100), - ]; -}); diff --git a/tests/Factories/PostFactory.php b/tests/Factories/PostFactory.php index 61fd7cebf..c9e4d745b 100644 --- a/tests/Factories/PostFactory.php +++ b/tests/Factories/PostFactory.php @@ -1,5 +1,6 @@ define(Binaryk\LaravelRestify\Tests\Fixtures\Post::class, function (Faker $faker) { +$factory->define(Post::class, function (Faker $faker) { return [ 'user_id' => 1, 'image' => $faker->imageUrl(), diff --git a/tests/Factories/UserFactory.php b/tests/Factories/UserFactory.php index d1f70b1d4..701a30777 100644 --- a/tests/Factories/UserFactory.php +++ b/tests/Factories/UserFactory.php @@ -1,5 +1,6 @@ define(Binaryk\LaravelRestify\Tests\Fixtures\User::class, function (Faker $faker) { +$factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, diff --git a/tests/AuthServiceForgotPasswordTest.php b/tests/Feature/Authentication/AuthServiceForgotPasswordTest.php similarity index 96% rename from tests/AuthServiceForgotPasswordTest.php rename to tests/Feature/Authentication/AuthServiceForgotPasswordTest.php index 26281602b..763ea23f3 100644 --- a/tests/AuthServiceForgotPasswordTest.php +++ b/tests/Feature/Authentication/AuthServiceForgotPasswordTest.php @@ -1,13 +1,14 @@ app['config']->set('restify.auth.provider', 'passport'); - $user = (new class extends \Binaryk\LaravelRestify\Tests\Fixtures\User { + $user = (new class extends \ Binaryk\LaravelRestify\Tests\Fixtures\User\User { public function tokens() { $builder = \Mockery::mock(Builder::class); diff --git a/tests/AuthServiceRegisterTest.php b/tests/Feature/Authentication/AuthServiceRegisterTest.php similarity index 93% rename from tests/AuthServiceRegisterTest.php rename to tests/Feature/Authentication/AuthServiceRegisterTest.php index 1f00a55b4..c2e292843 100644 --- a/tests/AuthServiceRegisterTest.php +++ b/tests/Feature/Authentication/AuthServiceRegisterTest.php @@ -1,6 +1,6 @@ assertEquals($e->user->email, $user['email']); - return $e->user instanceof \Binaryk\LaravelRestify\Tests\Fixtures\User; + return $e->user instanceof \ Binaryk\LaravelRestify\Tests\Fixtures\User\User; }); $lastUser = User::query()->get()->last(); @@ -145,7 +146,7 @@ public function test_verify_user_successfully() Event::assertDispatched(Verified::class, function ($e) use ($user) { $this->assertEquals($e->user->email, $user['email']); - return $e->user instanceof \Binaryk\LaravelRestify\Tests\Fixtures\User; + return $e->user instanceof \ Binaryk\LaravelRestify\Tests\Fixtures\User\User; }); } diff --git a/tests/ResetPasswordRequestTest.php b/tests/Feature/Authentication/ResetPasswordRequestTest.php similarity index 86% rename from tests/ResetPasswordRequestTest.php rename to tests/Feature/Authentication/ResetPasswordRequestTest.php index 7c428537c..38e08b3fc 100644 --- a/tests/ResetPasswordRequestTest.php +++ b/tests/Feature/Authentication/ResetPasswordRequestTest.php @@ -1,8 +1,9 @@ diff --git a/tests/RestifyLoginRequestTest.php b/tests/Feature/Authentication/RestifyLoginRequestTest.php similarity index 86% rename from tests/RestifyLoginRequestTest.php rename to tests/Feature/Authentication/RestifyLoginRequestTest.php index 730db81d3..bae708760 100644 --- a/tests/RestifyLoginRequestTest.php +++ b/tests/Feature/Authentication/RestifyLoginRequestTest.php @@ -1,8 +1,9 @@ diff --git a/tests/RestifyPasswordEmailRequestTest.php b/tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php similarity index 85% rename from tests/RestifyPasswordEmailRequestTest.php rename to tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php index 2cf416e84..c530e0ecc 100644 --- a/tests/RestifyPasswordEmailRequestTest.php +++ b/tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php @@ -1,8 +1,9 @@ diff --git a/tests/RestifyRegisterRequestTest.php b/tests/Feature/Authentication/RestifyRegisterRequestTest.php similarity index 87% rename from tests/RestifyRegisterRequestTest.php rename to tests/Feature/Authentication/RestifyRegisterRequestTest.php index 253c29cec..2d96e305e 100644 --- a/tests/RestifyRegisterRequestTest.php +++ b/tests/Feature/Authentication/RestifyRegisterRequestTest.php @@ -1,8 +1,9 @@ diff --git a/tests/HandlerTest.php b/tests/Feature/REST/HandlerTest.php similarity index 98% rename from tests/HandlerTest.php rename to tests/Feature/REST/HandlerTest.php index 03150c29c..ed43818d5 100644 --- a/tests/HandlerTest.php +++ b/tests/Feature/REST/HandlerTest.php @@ -1,6 +1,6 @@ - */ -class FieldResolversTest extends IntegrationTest -{ - public function test_show_callback_change_details_value() - { - tap($this->basicField(), function (Field $field) { - $book = factory(Book::class)->create(); - $repository = $this->basicRepository(); - $repository->resource = $book; - - $field->showCallback(function ($value, $repo) use ($book, $repository) { - $this->assertInstanceOf(get_class($repository), $repo); - $this->assertSame($value, $book->title); //assert that the value is read from the database - return 'something else'; - }); - - $this->assertSame($field->resolveForShow($repository)->value, 'something else'); - }); - } - - public function basicField() - { - return Field::make('title'); - } - - public function basicRepository() - { - return new class extends BookRepository { - }; - } -} diff --git a/tests/Fixtures/Apple.php b/tests/Fixtures/Apple.php deleted file mode 100644 index f7c4c0164..000000000 --- a/tests/Fixtures/Apple.php +++ /dev/null @@ -1,26 +0,0 @@ -belongsTo(User::class); - } - - public function toArray() - { - return parent::toArray(); - } -} diff --git a/tests/Fixtures/AppleRepository.php b/tests/Fixtures/AppleRepository.php deleted file mode 100644 index dc87a8e25..000000000 --- a/tests/Fixtures/AppleRepository.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class Book extends Model implements RestifySearchable -{ - use InteractWithSearch; - - protected $fillable = [ - 'id', - 'title', - 'description', - 'author', - 'price', - 'stock', - ]; -} diff --git a/tests/Fixtures/BookPolicy.php b/tests/Fixtures/BookPolicy.php deleted file mode 100644 index 255a5665b..000000000 --- a/tests/Fixtures/BookPolicy.php +++ /dev/null @@ -1,49 +0,0 @@ - - */ -class BookPolicy -{ - /** - * Determine if the given user can view resources. - */ - public function viewAny($user) - { - return $_SERVER['restify.book.viewAnyable'] ?? true; - } - - /** - * Determine if users can be created. - */ - public function store($user) - { - return $_SERVER['restify.book.storable'] ?? true; - } - - /** - * Determine if books can be updated. - */ - public function update($user, $book) - { - return $_SERVER['restify.book.updateable'] ?? true; - } - - /** - * Determine if book can be shown. - */ - public function show($user, $book) - { - return $_SERVER['restify.book.showable'] ?? true; - } - - /** - * Determine if books can be deleted. - */ - public function destroy($user) - { - return $_SERVER['restify.book.destroyable'] ?? true; - } -} diff --git a/tests/Fixtures/BookRepository.php b/tests/Fixtures/BookRepository.php deleted file mode 100644 index ef0db02e9..000000000 --- a/tests/Fixtures/BookRepository.php +++ /dev/null @@ -1,38 +0,0 @@ - - */ -class BookRepository extends Repository -{ - public static $model = Book::class; - - /** - * Get the URI key for the resource. - * - * @return string - */ - public static function uriKey() - { - return 'books'; - } - - /** - * @param RestifyRequest $request - * @return array - */ - public function fields(RestifyRequest $request) - { - return [ - Field::make('title')->storingRules('required')->messages([ - 'required' => 'This field is required', - ]), - ]; - } -} diff --git a/tests/Fixtures/Post.php b/tests/Fixtures/Post/Post.php similarity index 69% rename from tests/Fixtures/Post.php rename to tests/Fixtures/Post/Post.php index 7bce7006b..ffb7ce6c1 100644 --- a/tests/Fixtures/Post.php +++ b/tests/Fixtures/Post/Post.php @@ -1,8 +1,9 @@ belongsTo(User::class); + } } diff --git a/tests/Fixtures/Post/PostAuthorizeRepository.php b/tests/Fixtures/Post/PostAuthorizeRepository.php new file mode 100644 index 000000000..69a6c8183 --- /dev/null +++ b/tests/Fixtures/Post/PostAuthorizeRepository.php @@ -0,0 +1,29 @@ +canSee(fn () => $_SERVER['postAuthorize.can.see.title']) + ->showCallback(fn ($value) => strtoupper($value)), + + Field::new('description')->storingRules('required')->messages([ + 'required' => 'Description field is required', + ]), + ]; + } +} diff --git a/tests/Fixtures/PostMergeableRepository.php b/tests/Fixtures/Post/PostMergeableRepository.php similarity index 77% rename from tests/Fixtures/PostMergeableRepository.php rename to tests/Fixtures/Post/PostMergeableRepository.php index a5111c3cc..2a6a1eaf8 100644 --- a/tests/Fixtures/PostMergeableRepository.php +++ b/tests/Fixtures/Post/PostMergeableRepository.php @@ -1,6 +1,6 @@ @@ -9,10 +9,12 @@ class PostPolicy { /** * Determine if the given user can view resources. + * + * @return bool|mixed */ - public function viewAny($user) + public function allowRestify() { - return $_SERVER['restify.post.viewAnyable'] ?? true; + return $_SERVER['restify.post.allowRestify'] ?? true; } /** diff --git a/tests/Fixtures/PostRepository.php b/tests/Fixtures/Post/PostRepository.php similarity index 67% rename from tests/Fixtures/PostRepository.php rename to tests/Fixtures/Post/PostRepository.php index efe2dc225..6f34543aa 100644 --- a/tests/Fixtures/PostRepository.php +++ b/tests/Fixtures/Post/PostRepository.php @@ -1,6 +1,6 @@ canUpdate(fn () => $_SERVER['posts.authorizable.title']), + ]; + } +} diff --git a/tests/Fixtures/SimpleUser.php b/tests/Fixtures/User/SimpleUser.php similarity index 91% rename from tests/Fixtures/SimpleUser.php rename to tests/Fixtures/User/SimpleUser.php index 41cc12682..cf9964e48 100644 --- a/tests/Fixtures/SimpleUser.php +++ b/tests/Fixtures/User/SimpleUser.php @@ -1,6 +1,6 @@ push(factory(User::class)->create()); + $i++; + } + + foreach ($predefinedEmails as $email) { + $users->push(factory(User::class)->create([ + 'email' => $email, + ])); + } + + return $users->shuffle(); // randomly shuffles the items in the collection + } + + /** + * @param $userId + * @param int $count + * @return \Illuminate\Support\Collection + */ + public function mockPosts($userId, $count = 1) + { + $users = collect([]); + $i = 0; + while ($i < $count) { + $users->push(factory(Post::class)->create( + ['user_id' => $userId] + )); + $i++; + } + + return $users->shuffle(); // randomly shuffles the items in the collection + } } diff --git a/tests/InteractWithModels.php b/tests/InteractWithModels.php deleted file mode 100644 index 7aa7e5be0..000000000 --- a/tests/InteractWithModels.php +++ /dev/null @@ -1,54 +0,0 @@ - - */ -trait InteractWithModels -{ - /** - * @param int $count - * @param array $predefinedEmails - * @return \Illuminate\Support\Collection - */ - public function mockUsers($count = 1, $predefinedEmails = []) - { - $users = collect([]); - $i = 0; - while ($i < $count) { - $users->push(factory(User::class)->create()); - $i++; - } - - foreach ($predefinedEmails as $email) { - $users->push(factory(User::class)->create([ - 'email' => $email, - ])); - } - - return $users->shuffle(); // randomly shuffles the items in the collection - } - - /** - * @param $userId - * @param int $count - * @return \Illuminate\Support\Collection - */ - public function mockPosts($userId, $count = 1) - { - $users = collect([]); - $i = 0; - while ($i < $count) { - $users->push(factory(Post::class)->create( - ['user_id' => $userId] - )); - $i++; - } - - return $users->shuffle(); // randomly shuffles the items in the collection - } -} diff --git a/tests/Migrations/2019_12_22_000006_create_books_table.php b/tests/Migrations/2019_12_22_000006_create_books_table.php deleted file mode 100644 index 4c97e6f47..000000000 --- a/tests/Migrations/2019_12_22_000006_create_books_table.php +++ /dev/null @@ -1,36 +0,0 @@ -increments('id'); - $table->string('title'); - $table->longText('description')->nullable(); - $table->string('author')->nullable(); - $table->float('price')->default(0); - $table->integer('stock')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('books'); - } -} diff --git a/tests/Migrations/2020_05_04_000006_create_apples_table.php b/tests/Migrations/2020_05_04_000006_create_apples_table.php deleted file mode 100644 index 8524a932d..000000000 --- a/tests/Migrations/2020_05_04_000006_create_apples_table.php +++ /dev/null @@ -1,34 +0,0 @@ -increments('id'); - $table->string('title'); - $table->string('color')->nullable(); - $table->unsignedBigInteger('user_id')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('apples'); - } -} diff --git a/tests/Unit/FieldTest.php b/tests/Unit/FieldTest.php index e21a3460b..0c915010a 100644 --- a/tests/Unit/FieldTest.php +++ b/tests/Unit/FieldTest.php @@ -5,7 +5,7 @@ use Binaryk\LaravelRestify\Fields\Field; use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreRequest; use Binaryk\LaravelRestify\Http\Requests\RepositoryUpdateRequest; -use Binaryk\LaravelRestify\Tests\Fixtures\PostRepository; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\Route; @@ -40,13 +40,13 @@ public function test_fields_can_have_custom_show_callback() public function test_fields_can_have_custom_resolver_callback_even_if_field_is_missing() { - $field = Field::make('Name')->showCallback(function ($value, $model, $attribute) { - return strtoupper('default'); + $field = Field::make('Name')->resolveCallback(function ($value, $model, $attribute) { + return strtoupper($value); }); - $field->resolveForShow((object) ['name' => 'Binaryk'], 'email'); + $field->resolve((object) ['name' => 'Eduard'], 'name'); - $this->assertEquals('DEFAULT', $field->value); + $this->assertEquals('EDUARD', $field->value); } public function test_computed_fields_resolve() diff --git a/tests/RepositoryWithRoutesTest.php b/tests/Unit/RepositoryWithRoutesTest.php similarity index 96% rename from tests/RepositoryWithRoutesTest.php rename to tests/Unit/RepositoryWithRoutesTest.php index b433df6ae..abd78c7e4 100644 --- a/tests/RepositoryWithRoutesTest.php +++ b/tests/Unit/RepositoryWithRoutesTest.php @@ -1,10 +1,11 @@ group($options, function ($router) { $router->get('custom-namespace', 'HandleController@sayHello')->name('namespace.route');