4 | */
5 |
6 | .hljs {
7 | display: block;
8 | overflow-x: auto;
9 | padding: 0.5em;
10 | background: #1E1E1E;
11 | color: #DCDCDC;
12 | }
13 |
14 | .hljs-keyword,
15 | .hljs-literal,
16 | .hljs-symbol,
17 | .hljs-name {
18 | color: #569CD6;
19 | }
20 | .hljs-link {
21 | color: #569CD6;
22 | text-decoration: underline;
23 | }
24 |
25 | .hljs-built_in,
26 | .hljs-type {
27 | color: #4EC9B0;
28 | }
29 |
30 | .hljs-number,
31 | .hljs-class {
32 | color: #B8D7A3;
33 | }
34 |
35 | .hljs-string,
36 | .hljs-meta-string {
37 | color: #D69D85;
38 | }
39 |
40 | .hljs-regexp,
41 | .hljs-template-tag {
42 | color: #9A5334;
43 | }
44 |
45 | .hljs-subst,
46 | .hljs-function,
47 | .hljs-title,
48 | .hljs-params,
49 | .hljs-formula {
50 | color: #DCDCDC;
51 | }
52 |
53 | .hljs-comment,
54 | .hljs-quote {
55 | color: #57A64A;
56 | font-style: italic;
57 | }
58 |
59 | .hljs-doctag {
60 | color: #608B4E;
61 | }
62 |
63 | .hljs-meta,
64 | .hljs-meta-keyword,
65 | .hljs-tag {
66 | color: #9B9B9B;
67 | }
68 |
69 | .hljs-variable,
70 | .hljs-template-variable {
71 | color: #BD63C5;
72 | }
73 |
74 | .hljs-attr,
75 | .hljs-attribute,
76 | .hljs-builtin-name {
77 | color: #9CDCFE;
78 | }
79 |
80 | .hljs-section {
81 | color: gold;
82 | }
83 |
84 | .hljs-emphasis {
85 | font-style: italic;
86 | }
87 |
88 | .hljs-strong {
89 | font-weight: bold;
90 | }
91 |
92 | /*.hljs-code {
93 | font-family:'Monospace';
94 | }*/
95 |
96 | .hljs-bullet,
97 | .hljs-selector-tag,
98 | .hljs-selector-id,
99 | .hljs-selector-class,
100 | .hljs-selector-attr,
101 | .hljs-selector-pseudo {
102 | color: #D7BA7D;
103 | }
104 |
105 | .hljs-addition {
106 | background-color: #144212;
107 | display: inline-block;
108 | width: 100%;
109 | }
110 |
111 | .hljs-deletion {
112 | background-color: #600;
113 | display: inline-block;
114 | width: 100%;
115 | }
116 |
--------------------------------------------------------------------------------
/public/vendor/scribe/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/public/vendor/scribe/images/logo.png
--------------------------------------------------------------------------------
/public/vendor/scribe/images/navbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/public/vendor/scribe/images/navbar.png
--------------------------------------------------------------------------------
/public/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Laravel API Starter Kit
2 |
3 | [](https://packagist.org/packages/joselfonseca/laravel-api)
4 | [](https://packagist.org/packages/laravel/framework)
5 |
6 | 
7 |
8 | Laravel API starter Kit will provide you with the tools for making API's that everyone will love, API Authentication is already provided with passport.
9 |
10 | Here is a list of the packages installed:
11 |
12 | - [Laravel Passport](https://laravel.com/docs/8.x/passport)
13 | - [Laravel Socialite](https://laravel.com/docs/8.x/socialite)
14 | - [Laravel Fractal](https://github.com/spatie/laravel-fractal)
15 | - [Laravel Permission](https://github.com/spatie/laravel-permission)
16 | - [Intervention Image](http://image.intervention.io/)
17 |
18 | ## Installation
19 |
20 | To install the project you can use composer
21 |
22 | ```bash
23 | composer create-project joselfonseca/laravel-api new-api
24 | ```
25 |
26 | Modify the .env file to suit your needs
27 |
28 | ```
29 | APP_NAME=Laravel
30 | APP_ENV=local
31 | APP_KEY=
32 | APP_DEBUG=true
33 | APP_URL=http://localhost
34 |
35 | LOG_CHANNEL=stack
36 | LOG_LEVEL=debug
37 |
38 | DB_CONNECTION=mysql
39 | DB_HOST=127.0.0.1
40 | DB_PORT=3306
41 | DB_DATABASE=laravel
42 | DB_USERNAME=root
43 | DB_PASSWORD=
44 |
45 | BROADCAST_DRIVER=log
46 | CACHE_DRIVER=file
47 | QUEUE_CONNECTION=sync
48 | SESSION_DRIVER=file
49 | SESSION_LIFETIME=120
50 |
51 | MEMCACHED_HOST=127.0.0.1
52 |
53 | REDIS_HOST=127.0.0.1
54 | REDIS_PASSWORD=null
55 | REDIS_PORT=6379
56 |
57 | MAIL_MAILER=smtp
58 | MAIL_HOST=mailhog
59 | MAIL_PORT=1025
60 | MAIL_USERNAME=null
61 | MAIL_PASSWORD=null
62 | MAIL_ENCRYPTION=null
63 | MAIL_FROM_ADDRESS=null
64 | MAIL_FROM_NAME="${APP_NAME}"
65 |
66 | AWS_ACCESS_KEY_ID=
67 | AWS_SECRET_ACCESS_KEY=
68 | AWS_DEFAULT_REGION=us-east-1
69 | AWS_BUCKET=
70 |
71 | PUSHER_APP_ID=
72 | PUSHER_APP_KEY=
73 | PUSHER_APP_SECRET=
74 | PUSHER_APP_CLUSTER=mt1
75 |
76 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
77 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
78 | ```
79 |
80 | When you have the .env with your database connection set up you can run your migrations
81 |
82 | ```bash
83 | php artisan migrate
84 | ```
85 | Then run `php artisan passport:install`
86 |
87 | Run `php artisan db:seed` and you should have a new user with the roles and permissions set up
88 |
89 | ## Tests
90 |
91 | Navigate to the project root and run `vendor/bin/phpunit` after installing all the composer dependencies and after the .env file was created.
92 |
93 | ## API documentation
94 | The project uses API blueprint as API spec and [Aglio](https://github.com/danielgtaylor/aglio) to render the API docs, please install aglio and [merge-apib](https://github.com/ValeriaVG/merge-apib) in your machine and then you can run the following command to compile and render the API docs
95 | ```bash
96 | composer api-docs
97 | ```
98 |
99 | ## License
100 |
101 | The Laravel API Starter kit is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
102 |
--------------------------------------------------------------------------------
/resources/docs/.filemtimes:
--------------------------------------------------------------------------------
1 | # GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.
2 | # Scribe uses this file to know when you change something manually in your docs.
3 | resources/docs/groups/endpoints.md=1609361313
4 | resources/docs/index.md=1609361313
5 | resources/docs/authentication.md=1609361313
6 | resources/docs/groups/users.md=1609361313
--------------------------------------------------------------------------------
/resources/docs/authentication.md:
--------------------------------------------------------------------------------
1 | # Authenticating requests
2 |
3 | To authenticate requests, include an **`Authorization`** header with the value **`"Bearer {YOUR_AUTH_KEY}"`**.
4 |
5 | All authenticated endpoints are marked with a `requires authentication` badge in the documentation below.
6 |
7 | You can retrieve your token by login in with the passport routes.
8 |
--------------------------------------------------------------------------------
/resources/docs/groups/endpoints.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/resources/docs/groups/endpoints.md
--------------------------------------------------------------------------------
/resources/docs/groups/users.md:
--------------------------------------------------------------------------------
1 | # Users
2 |
3 |
4 | ## List users
5 |
6 |
7 | Returns the Users resource with the roles relation.
8 |
9 | > Example request:
10 |
11 | ```bash
12 | curl -X GET \
13 | -G "http://localhost/api/users" \
14 | -H "Content-Type: application/json" \
15 | -H "Accept: application/json"
16 | ```
17 |
18 | ```javascript
19 | const url = new URL(
20 | "http://localhost/api/users"
21 | );
22 |
23 | let headers = {
24 | "Content-Type": "application/json",
25 | "Accept": "application/json",
26 | };
27 |
28 |
29 | fetch(url, {
30 | method: "GET",
31 | headers,
32 | }).then(response => response.json());
33 | ```
34 |
35 | ```php
36 |
37 | $client = new \GuzzleHttp\Client();
38 | $response = $client->get(
39 | 'http://localhost/api/users',
40 | [
41 | 'headers' => [
42 | 'Content-Type' => 'application/json',
43 | 'Accept' => 'application/json',
44 | ],
45 | ]
46 | );
47 | $body = $response->getBody();
48 | print_r(json_decode((string) $body));
49 | ```
50 |
51 |
52 | > Example response (200):
53 |
54 | ```json
55 | {
56 | "data": [
57 | {
58 | "id": "fb8cab66-23e4-3f0b-8880-094f1ed59223",
59 | "name": "Mr. Dayton Pagac",
60 | "email": "urolfson@example.net",
61 | "created_at": "2020-12-30T20:48:33+00:00",
62 | "updated_at": "2020-12-30T20:48:33+00:00",
63 | "roles": {
64 | "data": []
65 | }
66 | },
67 | {
68 | "id": "6db61d41-17bc-3604-b7d5-83da76f9b03a",
69 | "name": "Annabel Senger",
70 | "email": "cstoltenberg@example.com",
71 | "created_at": "2020-12-30T20:48:33+00:00",
72 | "updated_at": "2020-12-30T20:48:33+00:00",
73 | "roles": {
74 | "data": []
75 | }
76 | }
77 | ],
78 | "meta": {
79 | "pagination": {
80 | "total": 2,
81 | "count": 2,
82 | "per_page": 20,
83 | "current_page": 1,
84 | "total_pages": 1,
85 | "links": {}
86 | }
87 | }
88 | }
89 | ```
90 |
91 |
Received response:
92 |
93 |
94 |
95 |
Request failed with error:
96 |
97 |
98 |
107 |
108 |
109 | ## Get single user
110 |
111 |
112 | Returns the User resource by it's uuid
113 |
114 | > Example request:
115 |
116 | ```bash
117 | curl -X GET \
118 | -G "http://localhost/api/users/minima" \
119 | -H "Content-Type: application/json" \
120 | -H "Accept: application/json"
121 | ```
122 |
123 | ```javascript
124 | const url = new URL(
125 | "http://localhost/api/users/minima"
126 | );
127 |
128 | let headers = {
129 | "Content-Type": "application/json",
130 | "Accept": "application/json",
131 | };
132 |
133 |
134 | fetch(url, {
135 | method: "GET",
136 | headers,
137 | }).then(response => response.json());
138 | ```
139 |
140 | ```php
141 |
142 | $client = new \GuzzleHttp\Client();
143 | $response = $client->get(
144 | 'http://localhost/api/users/minima',
145 | [
146 | 'headers' => [
147 | 'Content-Type' => 'application/json',
148 | 'Accept' => 'application/json',
149 | ],
150 | ]
151 | );
152 | $body = $response->getBody();
153 | print_r(json_decode((string) $body));
154 | ```
155 |
156 |
157 | > Example response (200):
158 |
159 | ```json
160 | {
161 | "data": {
162 | "id": "c6eaae09-1a89-33ed-93a5-6c8528d810d8",
163 | "name": "Kaelyn Lemke",
164 | "email": "alta.heller@example.com",
165 | "created_at": "2020-12-30T20:48:33+00:00",
166 | "updated_at": "2020-12-30T20:48:33+00:00",
167 | "roles": {
168 | "data": []
169 | }
170 | }
171 | }
172 | ```
173 |
174 |
Received response:
175 |
176 |
177 |
178 |
Request failed with error:
179 |
180 |
181 |
196 |
197 |
198 |
199 |
--------------------------------------------------------------------------------
/resources/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Laravel API
3 |
4 | language_tabs:
5 | - bash
6 | - javascript
7 | - php
8 |
9 | includes:
10 | - "./prepend.md"
11 | - "./authentication.md"
12 | - "./groups/*"
13 | - "./errors.md"
14 | - "./append.md"
15 |
16 | logo:
17 |
18 | toc_footers:
19 | - View Postman collection
20 | - View OpenAPI (Swagger) spec
21 | - Documentation powered by Scribe ✍
22 |
23 | ---
24 |
25 | # Introduction
26 |
27 | An API starter kit for your next project
28 |
29 | This documentation aims to provide all the information you need to work with our API.
30 |
31 |
33 |
34 |
35 | > Base URL
36 |
37 | ```yaml
38 | http://localhost
39 | ```
--------------------------------------------------------------------------------
/resources/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/resources/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/resources/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Your password has been reset!',
17 | 'sent' => 'We have e-mailed your password reset link!',
18 | 'token' => 'This password reset token is invalid.',
19 | 'user' => "We can't find a user with that e-mail address.",
20 | 'throttled' => 'Please wait before retrying.',
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | ['auth:api']], function () {
14 | Route::group(['prefix' => 'users'], function () {
15 | Route::get('/', 'Api\Users\UsersController@index');
16 | Route::post('/', 'Api\Users\UsersController@store');
17 | Route::get('/{uuid}', 'Api\Users\UsersController@show');
18 | Route::put('/{uuid}', 'Api\Users\UsersController@update');
19 | Route::patch('/{uuid}', 'Api\Users\UsersController@update');
20 | Route::delete('/{uuid}', 'Api\Users\UsersController@destroy');
21 | });
22 |
23 | Route::group(['prefix' => 'roles'], function () {
24 | Route::get('/', 'Api\Users\RolesController@index');
25 | Route::post('/', 'Api\Users\RolesController@store');
26 | Route::get('/{uuid}', 'Api\Users\RolesController@show');
27 | Route::put('/{uuid}', 'Api\Users\RolesController@update');
28 | Route::patch('/{uuid}', 'Api\Users\RolesController@update');
29 | Route::delete('/{uuid}', 'Api\Users\RolesController@destroy');
30 | });
31 |
32 | Route::get('permissions', 'Api\Users\PermissionsController@index');
33 |
34 | Route::group(['prefix' => 'me'], function () {
35 | Route::get('/', 'Api\Users\ProfileController@index');
36 | Route::put('/', 'Api\Users\ProfileController@update');
37 | Route::patch('/', 'Api\Users\ProfileController@update');
38 | Route::put('/password', 'Api\Users\ProfileController@updatePassword');
39 | });
40 |
41 | Route::group(['prefix' => 'assets'], function () {
42 | Route::post('/', 'Api\Assets\UploadFileController@store');
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | info('Token for user '.$user->name);
19 | $token = $user->createToken('Personal Access Token')->accessToken;
20 | $this->info($token);
21 | })->describe('Generates a personal access token for a user');
22 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | $uri = urldecode(
11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
12 | );
13 |
14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the
15 | // built-in PHP web server. This provides a convenient way to test a Laravel
16 | // application without having installed a "real" web server software here.
17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
18 | return false;
19 | }
20 |
21 | require_once __DIR__.'/public/index.php';
22 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | config.php
2 | routes.php
3 | schedule-*
4 | compiled.php
5 | services.json
6 | events.scanned.php
7 | routes.scanned.php
8 | down
9 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
20 |
21 | Hash::setRounds(4);
22 |
23 | return $app;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Feature/ApiDocsTest.php:
--------------------------------------------------------------------------------
1 | get('/')
14 | ->assertSeeText('The API uses conventional HTTP response codes to indicate the success or failure of an API request. The table below contains a summary of the typical response codes');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Feature/Assets/RenderFileTest.php:
--------------------------------------------------------------------------------
1 | create());
20 | $server = $this->transformHeadersToServerVars([
21 | 'Content-Type' => 'image/png',
22 | 'Content-Length' => mb_strlen($file),
23 | ]);
24 | $response = $this->call('POST', 'api/assets', [], [], [], $server, $file);
25 | $asset = json_decode($response->getContent());
26 | $response = $this->get('api/assets/'.$asset->data->id.'/render');
27 | $response->assertStatus(200);
28 | $headers = $response->headers;
29 | $this->assertTrue($headers->has('Content-Type'));
30 | $this->assertEquals('image/png', $headers->get('Content-Type'));
31 | }
32 |
33 | public function test_it_renders_placeholder_image()
34 | {
35 | Passport::actingAs(User::factory()->create());
36 | $response = $this->get('api/assets/'.Str::uuid().'/render');
37 | $response->assertStatus(200);
38 | $headers = $response->headers;
39 | $this->assertTrue($headers->has('Content-Type'));
40 | $this->assertEquals('image/jpeg', $headers->get('Content-Type'));
41 | }
42 |
43 | public function test_it_renders_placeholder_image_resized_to_width_100()
44 | {
45 | Passport::actingAs(User::factory()->create());
46 | $response = $this->get('api/assets/'.Str::uuid().'/render?width=100');
47 | $response->assertStatus(200);
48 | $headers = $response->headers;
49 | $this->assertTrue($headers->has('Content-Type'));
50 | $this->assertEquals('image/jpeg', $headers->get('Content-Type'));
51 | Storage::put('created.jpeg', $response->getContent());
52 | $size = getimagesize(storage_path('app/created.jpeg'));
53 | $this->assertEquals(100, $size[0]);
54 | $this->assertEquals(100, $size[1]);
55 | Storage::delete('created.jpeg');
56 | }
57 |
58 | public function test_it_renders_placeholder_image_resized_to_height_100()
59 | {
60 | Passport::actingAs(User::factory()->create());
61 | $response = $this->get('api/assets/'.Str::uuid().'/render?height=100');
62 | $response->assertStatus(200);
63 | $headers = $response->headers;
64 | $this->assertTrue($headers->has('Content-Type'));
65 | $this->assertEquals('image/jpeg', $headers->get('Content-Type'));
66 | Storage::put('created.jpeg', $response->getContent());
67 | $size = getimagesize(storage_path('app/created.jpeg'));
68 | $this->assertEquals(100, $size[0]);
69 | $this->assertEquals(100, $size[1]);
70 | Storage::delete('created.jpeg');
71 | }
72 |
73 | public function test_it_renders_placeholder_image_resized_to_width_and_height()
74 | {
75 | Passport::actingAs(User::factory()->create());
76 | $response = $this->get('api/assets/'.Str::uuid().'/render?height=100&width=300');
77 | $response->assertStatus(200);
78 | $headers = $response->headers;
79 | $this->assertTrue($headers->has('Content-Type'));
80 | $this->assertEquals('image/jpeg', $headers->get('Content-Type'));
81 | Storage::put('created.jpeg', $response->getContent());
82 | $size = getimagesize(storage_path('app/created.jpeg'));
83 | $this->assertEquals(300, $size[0]);
84 | $this->assertEquals(100, $size[1]);
85 | Storage::delete('created.jpeg');
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tests/Feature/Assets/UploadImageTest.php:
--------------------------------------------------------------------------------
1 | expectsEvents([AssetWasCreated::class]);
20 | $file = base64_encode(file_get_contents(base_path('tests/Resources/pic.png')));
21 | Passport::actingAs(User::factory()->create());
22 | $server = $this->transformHeadersToServerVars([
23 | 'Content-Type' => 'image/png',
24 | 'Content-Length' => mb_strlen($file),
25 | ]);
26 | $response = $this->call('POST', 'api/assets', [], [], [], $server, $file);
27 | $image = json_decode($response->getContent());
28 | $this->assertEquals(201, $response->getStatusCode());
29 | $response->assertJson([
30 | 'data' => [
31 | 'type' => 'image',
32 | 'mime' => 'image/png',
33 | ],
34 | ]);
35 | $this->assertDatabaseHas('assets', [
36 | 'type' => 'image',
37 | 'mime' => 'image/png',
38 | 'uuid' => $image->data->id,
39 | ]);
40 | $this->assertTrue(Storage::has($image->data->path));
41 | }
42 |
43 | public function test_it_verifies_max_file_size()
44 | {
45 | $this->doesntExpectEvents([AssetWasCreated::class]);
46 | Passport::actingAs(User::factory()->create());
47 | $file = base64_encode(file_get_contents(base_path('tests/Resources/bigpic.jpg')));
48 | $server = $this->transformHeadersToServerVars([
49 | 'Content-Type' => 'image/jpeg',
50 | 'Content-Length' => mb_strlen($file),
51 | 'Accept' => 'application/json',
52 | ]);
53 | $response = $this->call('POST', 'api/assets', [], [], [], $server, $file);
54 | $this->assertEquals(413, $response->getStatusCode());
55 | }
56 |
57 | public function test_it_validates_mime_type()
58 | {
59 | $this->doesntExpectEvents([AssetWasCreated::class]);
60 | Passport::actingAs(User::factory()->create());
61 | $server = $this->transformHeadersToServerVars([
62 | 'Content-Type' => 'application/xml',
63 | 'Content-Length' => mb_strlen('some ramdon content'),
64 | 'Accept' => 'application/json',
65 | ]);
66 | $response = $this->call('POST', 'api/assets', [], [], [], $server, 'some ramdon content');
67 | $this->assertEquals(422, $response->getStatusCode());
68 | $jsonResponse = json_decode($response->getContent(), true);
69 | $this->assertArrayHasKey('Content-Type', $jsonResponse['errors']);
70 | }
71 |
72 | public function test_it_uploads_from_url()
73 | {
74 | $this->expectsEvents([AssetWasCreated::class]);
75 | Passport::actingAs(User::factory()->create());
76 | $response = $this->json('POST', 'api/assets', [
77 | 'url' => 'https://images.unsplash.com/photo-1657299156538-e08595d224ca?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80',
78 | ]);
79 | $image = json_decode($response->getContent());
80 | $this->assertEquals(201, $response->getStatusCode());
81 | $response->assertJson([
82 | 'data' => [
83 | 'type' => 'image',
84 | 'mime' => 'image/jpeg',
85 | ],
86 | ]);
87 | $this->assertDatabaseHas('assets', [
88 | 'type' => 'image',
89 | 'mime' => 'image/jpeg',
90 | 'uuid' => $image->data->id,
91 | ]);
92 | $this->assertTrue(Storage::has($image->data->path));
93 | }
94 |
95 | public function test_it_respond_validation_unreachable_error_in_url()
96 | {
97 | $this->doesntExpectEvents([AssetWasCreated::class]);
98 | Passport::actingAs(User::factory()->create());
99 | $response = $this->json('POST', 'api/assets', [
100 | 'url' => 'http://somedomain/350x150',
101 | ], [
102 | 'Accept' => 'application/json',
103 | ]);
104 | $jsonResponse = json_decode($response->getContent(), true);
105 | $this->assertEquals(422, $response->getStatusCode());
106 | $this->assertArrayHasKey('url', $jsonResponse['errors']);
107 | }
108 |
109 | public function test_it_validates_mime_on_url()
110 | {
111 | $this->doesntExpectEvents([AssetWasCreated::class]);
112 | Passport::actingAs(User::factory()->create());
113 | $response = $this->json('POST', 'api/assets', [
114 | 'url' => 'http://google.com',
115 | ], [
116 | 'Accept' => 'application/json',
117 | ]);
118 | $jsonResponse = json_decode($response->getContent(), true);
119 | $this->assertEquals(422, $response->getStatusCode());
120 | $this->assertArrayHasKey('Content-Type', $jsonResponse['errors']);
121 | }
122 |
123 | public function test_it_validates_url()
124 | {
125 | Passport::actingAs(User::factory()->create());
126 | $response = $this->json('POST', 'api/assets', [
127 | 'url' => 'http://somedomain.com/350x150',
128 | ]);
129 | $jsonResponse = json_decode($response->getContent(), true);
130 | $this->assertEquals(422, $response->getStatusCode());
131 | $this->assertArrayHasKey('Content-Type', $jsonResponse['errors']);
132 | }
133 |
134 | public function test_it_validates_size_using_multipart_file()
135 | {
136 | Storage::fake();
137 | Passport::actingAs(User::factory()->create());
138 | config()->set('files.maxsize', 10);
139 | $file = UploadedFile::fake()->image('avatar.jpg')->size(1000);
140 | $response = $this->post('api/assets', [
141 | 'file' => $file,
142 | ], [
143 | 'Accept' => 'application/json',
144 | ]);
145 | $jsonResponse = json_decode($response->getContent(), true);
146 | $this->assertEquals(413, $response->getStatusCode());
147 | $this->assertArrayHasKey('message', $jsonResponse);
148 | $this->assertEquals('The body is too large', $jsonResponse['message']);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordsTest.php:
--------------------------------------------------------------------------------
1 | create();
25 | $this->postJson('/api/passwords/reset', [
26 | 'email' => $user->email,
27 | ])->assertCreated();
28 | Notification::assertSentTo($user, ResetPassword::class);
29 | Event::assertDispatched(ForgotPasswordRequested::class, function ($event) use ($user) {
30 | return $event->email === $user->email;
31 | });
32 | }
33 |
34 | public function test_it_validates_input()
35 | {
36 | Notification::fake();
37 | Event::fake([ForgotPasswordRequested::class]);
38 | $this->postJson('/api/passwords/reset', [])->assertStatus(422);
39 | Notification::assertNothingSent();
40 | Event::assertNotDispatched(ForgotPasswordRequested::class);
41 | }
42 |
43 | public function test_it_recovers_password_with_token()
44 | {
45 | Event::fake([PasswordRecovered::class]);
46 | $user = User::factory()->create();
47 | $broker = Password::broker();
48 | $token = $broker->createToken($user);
49 | DB::table('password_resets')->insert([
50 | 'email' => $user->email,
51 | 'token' => $token,
52 | 'created_at' => now(),
53 | ]);
54 | $this->putJson('/api/passwords/reset', [
55 | 'token' => $token,
56 | 'email' => $user->email,
57 | 'password' => 'Abc123**',
58 | ])->assertOk();
59 | Event::assertDispatched(PasswordRecovered::class, function ($event) use ($user) {
60 | return $event->user->id === $user->id;
61 | });
62 | }
63 |
64 | public function test_it_validates_token_in_password_reset()
65 | {
66 | Event::fake([PasswordRecovered::class]);
67 | $user = User::factory()->create();
68 | $this->putJson('/api/passwords/reset', [
69 | 'token' => 'some-token',
70 | 'email' => $user->email,
71 | 'password' => 'Abc123**',
72 | ])->assertStatus(422);
73 | Event::assertNotDispatched(PasswordRecovered::class);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/RegisterTest.php:
--------------------------------------------------------------------------------
1 | seed();
20 | $this->app->make(PermissionRegistrar::class)->registerPermissions();
21 | }
22 |
23 | public function test_it_register_user_with_role()
24 | {
25 | Event::fake([Registered::class]);
26 | $response = $this->json('POST', 'api/register/', [
27 | 'name' => 'John Doe',
28 | 'email' => 'john@example.com',
29 | 'password' => '12345678',
30 | 'password_confirmation' => '12345678',
31 | ]);
32 | $response->assertStatus(201);
33 | $this->assertDatabaseHas('users', [
34 | 'name' => 'John Doe',
35 | 'email' => 'john@example.com',
36 | ]);
37 | $user = User::where('email', 'john@example.com')->first();
38 | $this->assertTrue($user->hasRole('User'));
39 | Event::assertDispatched(Registered::class, function ($event) use ($user) {
40 | return $user->id === $event->user->id;
41 | });
42 | }
43 |
44 | public function test_it_validates_input_for_registration()
45 | {
46 | Event::fake([Registered::class]);
47 | $response = $this->json('POST', 'api/register', [
48 | 'name' => 'Some User',
49 | 'email' => 'some@email.com',
50 | 'password' => '123456789qq',
51 | ]);
52 | $response->assertStatus(422);
53 | $this->assertDatabaseMissing('users', [
54 | 'name' => 'Some User',
55 | 'email' => 'some@email.com',
56 | ]);
57 | Event::assertNotDispatched(Registered::class);
58 | }
59 |
60 | public function test_it_returns_422_on_validation_error()
61 | {
62 | Event::fake([Registered::class]);
63 | $response = $this->json('POST', 'api/register', [
64 | 'name' => 'Some User',
65 | ]);
66 | $response->assertStatus(422);
67 | $this->assertEquals('{"message":"The email field is required. (and 1 more error)","status_code":422,"errors":{"email":["The email field is required."],"password":["The password field is required."]}}', $response->getContent());
68 | $this->assertDatabaseMissing('users', [
69 | 'name' => 'Some User',
70 | 'email' => 'some@email.com',
71 | ]);
72 | Event::assertNotDispatched(Registered::class);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/SocialiteTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('getId')
23 | ->andReturn($provider->provider_id)
24 | ->shouldReceive('getName')
25 | ->andReturn($provider->user->name)
26 | ->shouldReceive('getEmail')
27 | ->andReturn($provider->user->email);
28 | Socialite::shouldReceive('driver->userFromToken')->andReturn($abstractUser);
29 | }
30 |
31 | public function mockSocialiteWithoutUser()
32 | {
33 | $abstractUser = Mockery::mock('Laravel\Socialite\Two\User');
34 | $abstractUser
35 | ->shouldReceive('getId')
36 | ->andReturn('fakeId')
37 | ->shouldReceive('getName')
38 | ->andReturn('Jose Fonseca')
39 | ->shouldReceive('getEmail')
40 | ->andReturn('jose@example.com');
41 | Socialite::shouldReceive('driver->userFromToken')->andReturn($abstractUser);
42 | }
43 |
44 | public function mockSocialiteWithUser(User $user)
45 | {
46 | $abstractUser = Mockery::mock('Laravel\Socialite\Two\User');
47 | $abstractUser
48 | ->shouldReceive('getId')
49 | ->andReturn('fakeId')
50 | ->shouldReceive('getName')
51 | ->andReturn('Jose Fonseca')
52 | ->shouldReceive('getEmail')
53 | ->andReturn($user->email);
54 | Socialite::shouldReceive('driver->userFromToken')->andReturn($abstractUser);
55 | }
56 |
57 | public function createClient()
58 | {
59 | return Client::factory()->create();
60 | }
61 |
62 | public function test_it_generates_tokens_with_social_grant_for_existing_user()
63 | {
64 | $provider = SocialProvider::factory()->create();
65 | $this->mockSocialite($provider);
66 | $client = $this->createClient();
67 | $response = $this->postJson('oauth/token', [
68 | 'grant_type' => 'social_grant',
69 | 'client_id' => $client->id,
70 | 'client_secret' => $client->secret,
71 | 'token' => Str::random(),
72 | 'provider' => 'github',
73 | ])->assertOk();
74 | $decodedResponse = json_decode($response->getContent(), true);
75 | $this->assertArrayHasKey('access_token', $decodedResponse);
76 | $this->assertArrayHasKey('refresh_token', $decodedResponse);
77 | }
78 |
79 | public function test_it_generates_tokens_with_social_grant_for_non_existing_user()
80 | {
81 | $this->mockSocialiteWithoutUser();
82 | $client = $this->createClient();
83 | $response = $this->postJson('oauth/token', [
84 | 'grant_type' => 'social_grant',
85 | 'client_id' => $client->id,
86 | 'client_secret' => $client->secret,
87 | 'token' => Str::random(),
88 | 'provider' => 'github',
89 | ])->assertOk();
90 | $decodedResponse = json_decode($response->getContent(), true);
91 | $this->assertArrayHasKey('access_token', $decodedResponse);
92 | $this->assertArrayHasKey('refresh_token', $decodedResponse);
93 | $this->assertDatabaseHas('users', [
94 | 'email' => 'jose@example.com',
95 | 'name' => 'Jose Fonseca',
96 | ]);
97 | $createdUser = User::first();
98 | $this->assertDatabaseHas('social_providers', [
99 | 'user_id' => $createdUser->id,
100 | 'provider' => 'github',
101 | 'provider_id' => 'fakeId',
102 | ]);
103 | }
104 |
105 | public function test_it_generates_tokens_with_social_grant_for_existing_user_without_social_provider()
106 | {
107 | $user = User::factory()->create();
108 | $this->mockSocialiteWithUser($user);
109 | $this->assertDatabaseMissing('social_providers', [
110 | 'user_id' => $user->id,
111 | ]);
112 | $client = $this->createClient();
113 | $response = $this->postJson('oauth/token', [
114 | 'grant_type' => 'social_grant',
115 | 'client_id' => $client->id,
116 | 'client_secret' => $client->secret,
117 | 'token' => Str::random(),
118 | 'provider' => 'github',
119 | ])->assertOk();
120 | $decodedResponse = json_decode($response->getContent(), true);
121 | $this->assertArrayHasKey('access_token', $decodedResponse);
122 | $this->assertArrayHasKey('refresh_token', $decodedResponse);
123 | $this->assertDatabaseHas('social_providers', [
124 | 'user_id' => $user->id,
125 | 'provider' => 'github',
126 | 'provider_id' => 'fakeId',
127 | ]);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/tests/Feature/PingTest.php:
--------------------------------------------------------------------------------
1 | json('GET', 'api/ping');
18 | $response->assertStatus(200);
19 | $response->assertJson(['status' => 'ok']);
20 | }
21 |
22 | public function test_it_returns_404()
23 | {
24 | $response = $this->json('GET', 'api/non-existing-resource');
25 | $response->assertStatus(404);
26 | $response->assertJson(['message' => '404 Not Found', 'status_code' => 404]);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Feature/Users/PermissionsEndpointsTest.php:
--------------------------------------------------------------------------------
1 | seed();
20 | $this->app->make(PermissionRegistrar::class)->registerPermissions();
21 | }
22 |
23 | public function test_it_can_list_permissions()
24 | {
25 | Passport::actingAs(User::first());
26 | $response = $this->json('GET', 'api/permissions');
27 | $response->assertStatus(200);
28 | $response->assertJson([
29 | 'data' => [
30 | ['name' => 'List users'],
31 | ['name' => 'Create users'],
32 | ],
33 | 'meta' => [
34 | 'pagination' => [
35 |
36 | ],
37 | ],
38 | ]);
39 | }
40 |
41 | public function test_it_can_list_paginated_permissions()
42 | {
43 | Permission::factory()->count(30)->create();
44 | Passport::actingAs(User::first());
45 | $response = $this->json('GET', 'api/permissions?limit=10');
46 | $response->assertStatus(200);
47 | $response->assertJson([
48 | 'data' => [
49 | ['name' => 'List users'],
50 | ['name' => 'Create users'],
51 | ],
52 | 'meta' => [
53 | 'pagination' => [
54 |
55 | ],
56 | ],
57 | ]);
58 | $jsonResponse = json_decode($response->getContent(), true);
59 | $queryString = explode('?', $jsonResponse['meta']['pagination']['links']['next']);
60 | parse_str($queryString[1], $result);
61 | $this->assertArrayHasKey('limit', $result);
62 | $this->assertEquals('10', $result['limit']);
63 | }
64 |
65 | public function test_it_prevents_unauthorized_list_permissions()
66 | {
67 | Passport::actingAs(User::factory()->create());
68 | $response = $this->json('GET', 'api/permissions');
69 | $response->assertStatus(403);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Feature/Users/ProfileEndpointsTest.php:
--------------------------------------------------------------------------------
1 | seed();
20 | $this->app->make(PermissionRegistrar::class)->registerPermissions();
21 | }
22 |
23 | public function test_it_gets_user_profile()
24 | {
25 | Passport::actingAs(User::first());
26 | $response = $this->json('GET', 'api/me');
27 | $response->assertStatus(200);
28 | $response->assertJson([
29 | 'data' => [
30 | 'name' => 'Jose Fonseca',
31 | 'email' => 'jose@example.com',
32 | 'roles' => [],
33 | ],
34 | ]);
35 | }
36 |
37 | public function test_it_can_update_logged_user_profile_all_entity()
38 | {
39 | Passport::actingAs(User::first());
40 | $response = $this->json('PUT', '/api/me', [
41 | 'name' => 'Jose Fonseca Edited',
42 | 'email' => 'jose@example.com',
43 | ]);
44 | $response->assertStatus(200);
45 | $this->assertDatabaseHas('users', [
46 | 'name' => 'Jose Fonseca Edited',
47 | 'email' => 'jose@example.com',
48 | ]);
49 | $response->assertJson([
50 | 'data' => [
51 | 'name' => 'Jose Fonseca Edited',
52 | 'email' => 'jose@example.com',
53 | 'roles' => [],
54 | ],
55 | ]);
56 | }
57 |
58 | public function test_it_can_update_profile_partial_entity()
59 | {
60 | Passport::actingAs(User::first());
61 | $response = $this->json('PATCH', '/api/me', [
62 | 'name' => 'Jose Fonseca Edited',
63 | ]);
64 | $response->assertStatus(200);
65 | $this->assertDatabaseHas('users', [
66 | 'name' => 'Jose Fonseca Edited',
67 | 'email' => 'jose@example.com',
68 | ]);
69 | $response->assertJson([
70 | 'data' => [
71 | 'name' => 'Jose Fonseca Edited',
72 | 'email' => 'jose@example.com',
73 | 'roles' => [],
74 | ],
75 | ]);
76 | }
77 |
78 | public function test_it_validates_input_for_update_profile()
79 | {
80 | Passport::actingAs(User::first());
81 | $response = $this->json('PATCH', '/api/me', [
82 | 'name' => '',
83 | ]);
84 | $response->assertStatus(422);
85 | }
86 |
87 | public function test_it_validates_input_for_email_on_update_profile()
88 | {
89 | Passport::actingAs(User::first());
90 | $user = User::factory()->create();
91 | $response = $this->json('PATCH', '/api/me', [
92 | 'email' => $user->email,
93 | ]);
94 | $response->assertStatus(422);
95 | }
96 |
97 | public function test_it_updates_logged_in_user_password()
98 | {
99 | $user = User::first();
100 | Passport::actingAs($user);
101 | $response = $this->json('PUT', '/api/me/password', [
102 | 'current_password' => 'password',
103 | 'password' => '123456789qq',
104 | 'password_confirmation' => '123456789qq',
105 | ]);
106 | $response->assertStatus(200);
107 | $this->assertTrue(app(Hasher::class)->check('123456789qq', $user->fresh()->password));
108 | }
109 |
110 | public function test_it_validates_input_to_update_logged_in_user_password_giving_wrong_current_pass()
111 | {
112 | $user = User::first();
113 | Passport::actingAs($user);
114 | $response = $this->json('PUT', '/api/me/password', [
115 | 'current_password' => 'secret1234345',
116 | 'password' => '123456789qq',
117 | 'password_confirmation' => '123456789qq',
118 | ]);
119 | $response->assertStatus(422);
120 | $this->assertFalse(app(Hasher::class)->check('123456789qq', $user->fresh()->password));
121 | }
122 |
123 | public function test_it_validates_input_to_update_logged_in_user_password()
124 | {
125 | $user = User::first();
126 | Passport::actingAs($user);
127 | $response = $this->json('PUT', '/api/me/password', [
128 | 'current_password' => 'secret1234',
129 | 'password' => '12345',
130 | 'password_confirmation' => '123456789qq',
131 | ]);
132 | $response->assertStatus(422);
133 | $this->assertFalse(app(Hasher::class)->check('123456789qq', $user->fresh()->password));
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/tests/Resources/bigpic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/tests/Resources/bigpic.jpg
--------------------------------------------------------------------------------
/tests/Resources/pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/laravel-api/4d46d1d4ebd29de95880ce1ab87c131f2ebb462a/tests/Resources/pic.png
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | create();
17 | $permissions = Permission::factory()->count(3)->create();
18 | $role->syncPermissions($permissions);
19 | $permissions->each(function ($permission) use ($role) {
20 | $this->assertDatabaseHas('role_has_permissions', [
21 | 'role_id' => $role->id,
22 | 'permission_id' => $permission->id,
23 | ]);
24 | });
25 | }
26 |
27 | public function test_it_syncs_permissions_by_array_of_names()
28 | {
29 | $role = Role::factory()->create();
30 | $permissions = Permission::factory()->count(3)->create();
31 | $role->syncPermissions($permissions->pluck('name')->toArray());
32 | $permissions->each(function ($permission) use ($role) {
33 | $this->assertDatabaseHas('role_has_permissions', [
34 | 'role_id' => $role->id,
35 | 'permission_id' => $permission->id,
36 | ]);
37 | });
38 | }
39 |
40 | public function test_it_syncs_permissions_by_array_of_uuids()
41 | {
42 | $role = Role::factory()->create();
43 | $permissions = Permission::factory()->count(3)->create();
44 | $role->syncPermissions($permissions->pluck('uuid')->toArray());
45 | $permissions->each(function ($permission) use ($role) {
46 | $this->assertDatabaseHas('role_has_permissions', [
47 | 'role_id' => $role->id,
48 | 'permission_id' => $permission->id,
49 | ]);
50 | });
51 | }
52 |
53 | public function test_it_can_fill_uuid_at_creation()
54 | {
55 | $uuid = '84e28c10-8991-11e7-ad89-056674746d73';
56 |
57 | $roleNotFilled = Role::factory()->create();
58 | $this->assertNotEquals($uuid, $roleNotFilled->uuid);
59 |
60 | $roleFilled = Role::factory()->create(['uuid' => $uuid]);
61 | $this->assertEquals($uuid, $roleFilled->uuid);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------