diff --git a/README.md b/README.md index 6a31e2f..82ccb77 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,6 @@ -# Laravel - Exercise 1 - Simple CRUD on REST +# PHP Laravel scaffolding for RealSkill - -## Summary - -{summary} - -Expected result of this task is an application which allows user to create/read/update/delete row in the table using REST requests. - -Sample book structure: -``` -[ - 'id' => 1, - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => 'Martin Fowler', - 'price' => '15.96' -] -``` - - -## Goals - -In order to complete this exercise you will need to follow these steps: - -1. Create `Book` model in `app/Book.php` with proper `namespace`. - -2. Create migration to create table `books` with columns listed below: - - * id - primary key, autoincrement - * title - type: `string`, length: 128, assertions before persisting data from controller: - * not blank - * min length = 3 - * max length = 128 - * author - type: `string`, length: 128, assertions before persisting data from controller: - * not blank - * min length = 3 - * max length = 128 - * price - type: `float`, assertions before persisting data from controller: - * numeric - * min value: 0 - -2. Create endpoint to retrieve list of books - * should be accessed via GET "/api/book" - * should return array of books as JSON - -3. Create endpoint to create new book - * should be accessed via POST "/api/book" - * should validate if payload is valid - * if payload is invalid it should return JSON with error messages per each property with **HTTP_UNPROCESSABLE_ENTITY** status code eg: - ``` - { - "author": "The author must be between 3 and 128 characters." - } - ``` - * if payload is valid it should store `Book` data and return JSON response data of newly created `Book`, eg.: - ``` - { - "id": 1, - "title": "Test Driven Development: By Example", - "author": "Kent Beck", - "price": "39.51", - "created_at": "2016-11-07 19:26:55", - "updated_at": "2016-11-07 19:26:55" - } - ``` - -4. Create endpoint to update existing book - * should be accessed via PUT "/api/book/{bookId}" - * should validate if payload is valid - * should assign title, author from request to `Book` object - * if `Book` with given `bookId` not exist it should return **NOT_FOUND** status code - * if payload is invalid it should return JSON with error messages per each property with **HTTP_UNPROCESSABLE_ENTITY** status code eg: - ``` - { - "author": "The author must be between 3 and 128 characters." - } - ``` - * if payload is valid it should store `Book` data and return JSON response data of newly created `Book`, eg.: - ``` - { - "id": 1, - "title": "Test Driven Development: By Example", - "author": "Kent Beck", - "price": "39.51", - "created_at": "2016-11-07 19:26:55", - "updated_at": "2016-11-07 19:26:55" - } - ``` - -5. Create endpoint to delete existing book - * should be accessed via DELETE "/api/book/{bookId}" - * if `Book` with given `bookId` not exist it should **NOT_FOUND** status code - * if `Book` with given `bookId` exist it should delete it and return **HTTP_ACCEPTED** status code - -Expected result of `php composer test-dox` for completed exercise is listed below: -``` -CreateBook - [x] Should not save book with empty payload - [x] Should not save book with empty title - [x] Should not save book with empty author - [x] Should not save book with empty price - [x] Should not save book with too short title - [x] Should not save book with too long title - [x] Should not save book with too short author - [x] Should not save book with too long author - [x] Should not save book with negative price - [x] Should not save book with invalid string price - [x] Should save book with proper payload and return its data - - -DeleteBook - [x] Should return 404 status code if book not exists - [x] Should return certain book data if book exists - -RetrieveBook - [x] Should return 404 status code if book not exists - [x] Should return certain book data if book exists - -RetrieveBooks - [x] Should return books data - -UpdateBook - [x] Should save book with proper payload and return its data - [x] Should not save book with empty payload - [x] Should not save book with empty title - [x] Should not save book with empty author - [x] Should not save book with empty price - [x] Should not save book with too short title - [x] Should not save book with too long title - [x] Should not save book with too short author - [x] Should not save book with too long author - [x] Should not save book with negative price - [x] Should not save book with invalid string price -``` - - -## Hints - -Most of changes should lay in `app` dir. You can also modify files in `database/migrations`, `routes` and `resources`. - -If You want to see what goals You have passed You should run: `php composer test-dox`. Each scenario with **[x]** has passed and those with **[ ]** has to be done. - -More info about errors during tests You can get running tests with command: `php composer test` - -This task is concerned as done when all tests are passing and when code-sniffer and mess-detector do not return errors nor warnings (ignore info about "Remaining deprecation notices" during test). - -Remember to commit changes before You change branch. - -Remember to install dependencies if You change branch. +You can quickly create Laravel tasks by cloning the scaffolding repo. You don't have to bother with configuration. ### Helpful links @@ -160,7 +14,6 @@ Please remember to read documentation for Laravel 5.3 because it can differ in n * In some cases it may be required to install **xml** extension for php (`sudo apt-get install php-xml`). Especially if you see **Attempt to load class "DOMDocument" from global namespace** * You must have installed **MySQL** or **MariaDB** or run it using docker (see below in Setup/Database configuration) - ## Setup ### To install dependencies @@ -183,7 +36,6 @@ Please remember to read documentation for Laravel 5.3 because it can differ in n php composer code-sniffer - ## Run php server php artisan serve @@ -203,7 +55,6 @@ mysql> grant usage on *.* to realskill@localhost identified by 'realskill'; mysql> grant all privileges on realskill.* to realskill@localhost ; ``` - **Now You can access website via http://127.0.0.1:8000** Good luck! diff --git a/app/Http/Controllers/Auth/.gitkeep b/app/Http/Controllers/Auth/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php deleted file mode 100644 index 6a247fe..0000000 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ /dev/null @@ -1,32 +0,0 @@ -middleware('guest'); - } -} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php deleted file mode 100644 index 7594953..0000000 --- a/app/Http/Controllers/Auth/LoginController.php +++ /dev/null @@ -1,39 +0,0 @@ -middleware('guest', ['except' => 'logout']); - } -} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php deleted file mode 100644 index e48e2e3..0000000 --- a/app/Http/Controllers/Auth/RegisterController.php +++ /dev/null @@ -1,71 +0,0 @@ -middleware('guest'); - } - - /** - * Get a validator for an incoming registration request. - * - * @param array $data - * @return \Illuminate\Contracts\Validation\Validator - */ - protected function validator(array $data) - { - return Validator::make($data, [ - 'name' => 'required|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|min:6|confirmed', - ]); - } - - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * @return User - */ - protected function create(array $data) - { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => bcrypt($data['password']), - ]); - } -} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php deleted file mode 100644 index cf726ee..0000000 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ /dev/null @@ -1,39 +0,0 @@ -middleware('guest'); - } -} diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 1bb029a..bcedce8 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -8,47 +8,6 @@ class BookController extends Controller { - /** - * Display a listing of the resource. - */ - public function index() - { - return Book::all(); - } - - /** - * Store a newly created resource in storage. - * - * @param StoreBook $request - * @return array|Book - */ - public function store(StoreBook $request) - { - } - - /** - * Display the specified resource. - * - * @param Book $book - * @return Book - */ - public function show(Book $book) - { - return $book; - } - - /** - * Update the specified resource in storage. - * - * @param StoreBook $request - * @param Book $book - * @return Book - */ - public function update(StoreBook $request, Book $book) - { - return $book; - } - /** * Remove the specified resource from storage. * diff --git a/app/Http/Middleware/.gitkeep b/app/Http/Middleware/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php deleted file mode 100644 index 3aa15f8..0000000 --- a/app/Http/Middleware/EncryptCookies.php +++ /dev/null @@ -1,17 +0,0 @@ -check()) { - return redirect('/home'); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php deleted file mode 100644 index a2c3541..0000000 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ /dev/null @@ -1,17 +0,0 @@ - 'required|between:3,128', - 'author' => 'required|between:3,128', - 'price' => 'required|numeric|min:0', - ]; - } -} diff --git a/app/User.php b/app/User.php deleted file mode 100644 index bfd96a6..0000000 --- a/app/User.php +++ /dev/null @@ -1,29 +0,0 @@ - [ 'driver' => 'mysql', - 'host' => env('MARIADB_HOST', 'localhost'), + 'host' => env('MARIADB_HOST', 'mariadb'), 'port' => env('DB_PORT', '3306'), - 'database' => env('MARIADB_DATABASE', 'forge'), - 'username' => env('MARIADB_USER', 'forge'), - 'password' => env('MARIADB_PASSWORD', ''), + 'database' => env('MARIADB_DATABASE', 'realskill'), + 'username' => env('MARIADB_USER', 'realskill'), + 'password' => env('MARIADB_PASSWORD', 'realskill'), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', diff --git a/package.json b/package.json index 5828737..2ab315b 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,14 @@ "dev": "gulp watch" }, "devDependencies": { - "bootstrap-sass": "^3.3.7", - "gulp": "^3.9.1", - "jquery": "^3.1.0", - "laravel-elixir": "^6.0.0-11", - "laravel-elixir-vue-2": "^0.2.0", - "laravel-elixir-webpack-official": "^1.0.2", - "lodash": "^4.16.2", - "vue": "^2.0.1", - "vue-resource": "^1.0.3" + "bootstrap-sass": "3.3.7", + "gulp": "3.9.1", + "jquery": "3.1.0", + "laravel-elixir": "6.0.0-11", + "laravel-elixir-vue-2": "0.2.0", + "laravel-elixir-webpack-official": "1.0.2", + "lodash": "4.16.2", + "vue": "2.0.1", + "vue-resource": "1.0.3" } } diff --git a/tests/CreateBookTest.php b/tests/CreateBookTest.php deleted file mode 100644 index 13de672..0000000 --- a/tests/CreateBookTest.php +++ /dev/null @@ -1,250 +0,0 @@ -seed(BooksTableSeeder::class); - } - - /** - * @test - */ - public function shouldNotSaveBookWithEmptyPayload() - { - $this->json($this->method, $this->uri, []) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'author' => ['The author field is required.'], - 'title' => ['The title field is required.'], - 'price' => ['The price field is required.'], - ]); - - $this->assertEquals(2, Book::count()); - } - - /** - * @test - */ - public function shouldNotSaveBookWithEmptyTitle() - { - $payload = [ - 'author' => 'Martin Fowler', - 'price' => 54.03, - ]; - - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'title' => ['The title field is required.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithEmptyAuthor() - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'price' => 54.03, - ]; - - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'author' => ['The author field is required.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithEmptyPrice() - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => 'Martin Fowler', - ]; - - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'price' => ['The price field is required.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithTooShortTitle() - { - $payload = [ - 'title' => 'hq', - 'author' => 'Martin Fowler', - 'price' => 54.03, - ]; - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'title' => ['The title must be between 3 and 128 characters.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithTooLongTitle() - { - $payload = [ - 'title' => str_repeat('h', 129), - 'author' => 'Martin Fowler', - 'price' => 54.03, - ]; - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'title' => ['The title must be between 3 and 128 characters.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithTooShortAuthor() - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => 'hq', - 'price' => 54.03, - ]; - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'author' => ['The author must be between 3 and 128 characters.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithTooLongAuthor() - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => str_repeat('h', 129), - 'price' => 54.03, - ]; - - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'author' => ['The author must be between 3 and 128 characters.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithNegativePrice() - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => 'Martin Fowler', - 'price' => -0.01, - ]; - - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'price' => ['The price must be at least 0.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - */ - public function shouldNotSaveBookWithInvalidStringPrice() - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => 'Martin Fowler', - 'price' => 'hq', - ]; - - $this->json($this->method, $this->uri, $payload) - ->assertResponseStatus(Response::HTTP_UNPROCESSABLE_ENTITY) - ->seeJson([ - 'price' => ['The price must be a number.'], - ]); - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - } - - /** - * @test - * - * @param int $booksCountOnSuccess - * @return array - */ - public function shouldSaveBookWithProperPayloadAndReturnItsData($booksCountOnSuccess = 3) - { - $payload = [ - 'title' => 'Patterns of Enterprise Application Architecture', - 'author' => 'Martin Fowler', - 'price' => 54.03 - ]; - - $this->assertEquals(2, Book::count()); - $this->notSeeInDatabase('books', $payload); - - $this->json($this->method, $this->uri, $payload) - ->assertResponseOk() - ->seeJsonStructure(['id', 'created_at', 'updated_at', 'title', 'author']) - ->seeJson($payload); - - $this->seeInDatabase('books', $payload); - $this->assertEquals($booksCountOnSuccess, Book::count()); - - return $payload; - } -} diff --git a/tests/DeleteBookTest.php b/tests/DeleteBookTest.php index c0f84f2..2361622 100644 --- a/tests/DeleteBookTest.php +++ b/tests/DeleteBookTest.php @@ -28,22 +28,4 @@ public function shouldReturn404StatusCodeIfBookNotExists() $this->json('DELETE', '/api/book/3') ->assertResponseStatus(Response::HTTP_NOT_FOUND); } - - /** - * @test - */ - public function shouldReturnCertainBookDataIfBookExists() - { - $this->assertEquals(2, App\Book::count()); - - $this->seeInDatabase('books', ['id' => 1]); - $this->seeInDatabase('books', ['id' => 2]); - - $this->json('DELETE', '/api/book/1')->assertResponseStatus(Response::HTTP_ACCEPTED); - $this->notSeeInDatabase('books', ['id' => 1]); - $this->seeInDatabase('books', ['id' => 2]); - - $this->json('DELETE', '/api/book/2')->assertResponseStatus(Response::HTTP_ACCEPTED); - $this->notSeeInDatabase('books', ['id' => 2]); - } } diff --git a/tests/RetrieveBookTest.php b/tests/RetrieveBookTest.php deleted file mode 100644 index 23042df..0000000 --- a/tests/RetrieveBookTest.php +++ /dev/null @@ -1,78 +0,0 @@ -seed(BooksTableSeeder::class); - } - - /** - * @test - */ - public function shouldReturn404StatusCodeIfBookNotExists() - { - $this->assertEquals(2, App\Book::count()); - - $this->notSeeInDatabase('books', ['id' => 3]); - $this->json('GET', '/api/book/3') - ->assertResponseStatus(Response::HTTP_NOT_FOUND); - } - - /** - * @test - */ - public function shouldReturnCertainBookDataIfBookExists() - { - $this->assertEquals(2, App\Book::count()); - - $this->seeInDatabase('books', ['id' => 1]); - $this->json('GET', '/api/book/1') - ->assertResponseOk() - ->seeJsonStructure([ - 'id', - 'title', - 'author', - 'price', - 'created_at', - 'updated_at', - ]) - ->seeJsonEquals([ - 'id' => 1, - 'title' => 'Test Driven Development: By Example', - 'author' => 'Kent Beck', - 'price' => '39.51', - 'created_at' => '2016-11-07 19:26:55', - 'updated_at' => '2016-11-07 19:26:55', - ]); - $this->seeInDatabase('books', ['id' => 2]); - $this->json('GET', '/api/book/2') - ->assertResponseOk() - ->seeJsonStructure([ - 'id', - 'title', - 'author', - 'price', - 'created_at', - 'updated_at', - ]) - ->seeJsonEquals([ - 'id' => 2, - 'title' => 'Clean Code: A Handbook of Agile Software Craftsmanship', - 'author' => 'Robert C. Martin', - 'price' => '41.60', - 'created_at' => '2016-11-08 19:26:55', - 'updated_at' => '2016-11-08 19:26:55', - ]); - } -} diff --git a/tests/RetrieveBooksTest.php b/tests/RetrieveBooksTest.php deleted file mode 100644 index 57c45c5..0000000 --- a/tests/RetrieveBooksTest.php +++ /dev/null @@ -1,57 +0,0 @@ -seed(BooksTableSeeder::class); - } - - /** - * @test - */ - public function shouldReturnBooksData() - { - $this->assertEquals(2, App\Book::count()); - - $this->json('GET', '/api/book') - ->assertResponseOk() - ->seeJsonStructure([ - '*' => [ - 'id', - 'title', - 'author', - 'price', - 'created_at', - 'updated_at', - ], - ]) - ->seeJsonEquals([ - [ - 'id' => 1, - 'title' => 'Test Driven Development: By Example', - 'author' => 'Kent Beck', - 'price' => '39.51', - 'created_at' => '2016-11-07 19:26:55', - 'updated_at' => '2016-11-07 19:26:55', - ], - [ - 'id' => 2, - 'title' => 'Clean Code: A Handbook of Agile Software Craftsmanship', - 'author' => 'Robert C. Martin', - 'price' => '41.60', - 'created_at' => '2016-11-08 19:26:55', - 'updated_at' => '2016-11-08 19:26:55', - ], - ]); - } -} diff --git a/tests/UpdateBookTest.php b/tests/UpdateBookTest.php deleted file mode 100644 index d9a4d91..0000000 --- a/tests/UpdateBookTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertEquals($payload, [ - 'title' => $book->title, - 'author' => $book->author, - 'price' => $book->price, - ]); - } -}