diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 8bcea25576..06008f55b7 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -6,6 +6,7 @@ use Exception; use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use App\Helpers\Helper; +use Illuminate\Validation\ValidationException; class Handler extends ExceptionHandler { @@ -40,7 +41,7 @@ class Handler extends ExceptionHandler * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request - * @param \Exception $exception + * @param \Exception $e * @return \Illuminate\Http\Response */ public function render($request, Exception $e) @@ -61,6 +62,10 @@ class Handler extends ExceptionHandler return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200); } + if ($e instanceof \Illuminate\Validation\ValidationException) { + return response()->json(Helper::formatStandardApiResponse('error', $e->response['messages'], $e->getMessage(), 400)); + } + if ($this->isHttpException($e)) { $statusCode = $e->getStatusCode(); diff --git a/routes/api.php b/routes/api.php index 3e534d76ad..68cc3e9e73 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,9 +1,6 @@ 'v1','namespace' => 'Api'], function () { - /*---Hardware API---*/ - - Route::resource('users', 'UsersController', - ['names' => - [ - 'index' => 'api.users.index', - 'show' => 'api.users.show', - 'update' => 'api.users.update', - 'store' => 'api.users.store', - 'destroy' => 'api.users.destroy' - ], - 'except' => ['edit'], - 'parameters' => ['user' => 'user_id'] - ] - ); - - Route::resource('licenses', 'LicensesController', - ['names' => - [ - 'index' => 'api.licenses.index', - 'show' => 'api.licenses.show', - 'update' => 'api.licenses.update', - 'store' => 'api.licenses.store', - 'destroy' => 'api.licenses.destroy' - ], - 'except' => ['edit'], - 'parameters' => ['license' => 'license_id'] - ] - ); - Route::post('imports/process/{import_id}', [ 'as' => 'api.imports.importFile', 'uses'=> 'ImportController@process']); - - Route::resource('imports', 'ImportController', - ['names' => - [ - 'index' => 'api.imports.index', - 'show' => 'api.imports.show', - 'update' => 'api.imports.update', - 'store' => 'api.imports.store', - 'destroy' => 'api.imports.destroy' - ], - 'except' => ['edit'] - ] - ); - - - Route::get('models/{id}/assets', - [ - 'as' => 'api.models.assets', - 'uses'=> 'AssetModelsController@assets' - ]); - - - Route::resource('models', 'AssetModelsController', - ['names' => - [ - 'index' => 'api.models.index', - 'show' => 'api.models.show', - 'update' => 'api.models.update', - 'store' => 'api.models.store', - 'destroy' => 'api.models.destroy' - ], - 'except' => ['edit', 'create'], - 'parameters' => ['model' => 'model_id'] - ] - ); - - Route::resource('categories', 'CategoriesController', - ['names' => - [ - 'index' => 'api.categories.index', - 'show' => 'api.categories.asset.show', - 'update' => 'api.categories.update', - 'store' => 'api.categories.store', - 'destroy' => 'api.categories.destroy' - ], - 'except' => ['edit', 'create'], - 'parameters' => ['category' => 'category_id'] - ] - ); - - - Route::resource('companies', 'CompaniesController', - ['names' => - [ - 'index' => 'api.companies.index', - 'show' => 'api.companies.show', - 'update' => 'api.companies.update', - 'store' => 'api.companies.store', - 'destroy' => 'api.companies.destroy' - ], - 'except' => ['edit'], - 'parameters' => ['component' => 'component_id'] - ] - ); - - Route::resource('locations', 'LocationsController', - ['names' => - [ - 'index' => 'api.locations.index', - 'show' => 'api.locations.show', - 'update' => 'api.locations.update', - 'store' => 'api.locations.store', - 'destroy' => 'api.locations.destroy' - ], - 'except' => ['edit'], - 'parameters' => ['locations' => 'locations_id'] - ] - ); - - - Route::resource('components', 'ComponentsController', - ['names' => - [ - 'index' => 'api.components.index', - 'show' => 'api.components.show', - 'update' => 'api.components.update', - 'store' => 'api.components.store', - 'destroy' => 'api.components.destroy' - ], - 'parameters' => - ['component' => 'component_id'] - ] - ); - - Route::get('components/{id}/assets', [ - 'as' =>'api.components.assets', - 'uses' => 'ComponentsController@getAssets', - ]); - - - Route::resource('suppliers', 'SuppliersController', - ['names' => - [ - 'index' => 'api.suppliers.index', - 'create' => 'api.suppliers.create', - 'destroy' => 'api.suppliers.destroy' - ], - 'parameters' => - ['supplier' => 'supplier_id'] - ] - ); - - - Route::resource('groups', 'GroupsController', - ['names' => - [ - 'index' => 'api.groups.index', - 'create' => 'api.groups.create', - 'store' => 'api.groups.store', - 'destroy' => 'api.groups.destroy' - ], - 'parameters' => - ['group' => 'group_id'] - ] - ); - - - Route::resource('depreciations', 'DepreciationsController', - ['names' => - [ - 'index' => 'api.depreciations.index', - 'create' => 'api.depreciations.create', - 'destroy' => 'api.depreciations.destroy' - ], - 'parameters' => - ['depreciation' => 'depreciation_id'] - ] - ); - - - Route::resource('users', 'UsersController', - ['names' => - [ - 'index' => 'api.users.index', - 'create' => 'api.users.create', - 'destroy' => 'api.users.destroy' - ], - 'parameters' => - ['user' => 'user_id'] - ] - ); - - Route::resource('settings', 'SettingsController', - ['names' => - [ - 'index' => 'api.settings.index', - 'create' => 'api.settings.create' - ], - 'parameters' => - ['setting' => 'setting_id'] - ] - ); - - - - - - /*---Status Label API---*/ - Route::group([ 'prefix' => 'statuslabels'], function () { - - - Route::get('{id}/assetlist', - [ 'as' => 'api.statuslabels.assets', 'uses' => 'StatuslabelsController@assets' ]); - - Route::get('{id}/deployable', - [ 'as' => 'api.statuslabels.deployable', 'uses' => 'StatuslabelsController@checkIfDeployable' ]); - - - - // Pie chart for dashboard - Route::get('assets', [ 'as' => 'api.statuslabels.assets.bytype', 'uses' => 'StatuslabelsController@getAssetCountByStatuslabel' ]); - - }); - - - Route::resource('statuslabels', 'StatuslabelsController', - ['names' => - [ - 'index' => 'api.statuslabels.index', - 'create' => 'api.statuslabels.create', - 'destroy' => 'api.statuslabels.destroy' - ], - 'parameters' => - ['statuslabel' => 'statuslabel_id'] - ] - ); - - - - Route::resource('consumables', 'ConsumablesController', - ['names' => - [ - 'index' => 'api.consumables.index', - 'create' => 'api.consumables.create', - 'show' => 'api.consumables.show', - 'destroy' => 'api.consumables.destroy' - ], - 'parameters' => - ['consumable' => 'consumable_id'] - ] - ); - - Route::resource('manufacturers', 'ManufacturersController', - ['names' => - [ - 'index' => 'api.manufacturers.index', - 'show' => 'api.manufacturers.show', - 'update' => 'api.manufacturers.update', - 'store' => 'api.manufacturers.store', - 'destroy' => 'api.manufacturers.destroy' - ], - 'except' => ['edit'], - 'parameters' => ['manufacturer' => 'manufacturer_id'] - ] - ); - - - Route::group([ 'prefix' => 'accessories' ], function () { - Route::match(['DELETE'], '{id}', ['uses' => 'AccessoriesController@destroy','as' => 'api.accessories.destroy']); - Route::get( - '{id}/checkedout', - [ 'as' => 'api.accessories.checkedout', 'uses' => 'AccessoriesController@checkedout' ] - ); - - }); - + /*--- Accessories API ---*/ Route::resource('accessories', 'AccessoriesController', ['names' => @@ -295,72 +27,478 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () { 'store' => 'api.accessories.store', 'destroy' => 'api.accessories.destroy' ], - 'except' => ['edit'], + 'except' => ['create', 'edit'], 'parameters' => ['accessory' => 'accessory_id'] ] + ); // Accessories resource + + Route::group(['prefix' => 'accessories'], function () { + + Route::get('{accessory}/checkedout', + [ + 'as' => 'api.accessories.checkedout', + 'uses' => 'AccessoriesController@checkedout' + ] + ); + }); // Accessories group + + + /*--- Categories API ---*/ + + Route::resource('categories', 'CategoriesController', + [ + 'names' => + [ + 'index' => 'api.categories.index', + 'show' => 'api.categories.show', + 'store' => 'api.categories.store', + 'update' => 'api.categories.update', + 'destroy' => 'api.categories.destroy' + ], + 'except' => ['edit', 'create'], + 'parameters' => ['category' => 'category_id'] + ] + ); // Categories resource + + + /*--- Companies API ---*/ + + Route::resource('companies', 'CompaniesController', + [ + 'names' => + [ + 'index' => 'api.companies.index', + 'show' => 'api.companies.show', + 'store' => 'api.companies.store', + 'update' => 'api.companies.update', + 'destroy' => 'api.companies.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['component' => 'component_id'] + ] + ); // Companies resource + + + /*--- Components API ---*/ + + Route::resource('components', 'ComponentsController', + [ + 'names' => + [ + 'index' => 'api.components.index', + 'show' => 'api.components.show', + 'store' => 'api.components.store', + 'update' => 'api.components.update', + 'destroy' => 'api.components.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['component' => 'component_id'] + ] + ); // Components resource + + Route::group(['prefix' => 'components'], function () { + + Route::get('{component}/assets', + [ + 'as' =>'api.components.assets', + 'uses' => 'ComponentsController@getAssets', + ] + ); + }); // Components group + + + /*--- Consumables API ---*/ + + Route::resource('consumables', 'ConsumablesController', + [ + 'names' => + [ + 'index' => 'api.consumables.index', + 'show' => 'api.consumables.show', + 'store' => 'api.consumables.store', + 'update' => 'api.consumables.update', + 'destroy' => 'api.consumables.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['consumable' => 'consumable_id'] + ] + ); // Consumables resource + + + /*--- Depreciations API ---*/ + + Route::resource('depreciations', 'DepreciationsController', + [ + 'names' => + [ + 'index' => 'api.depreciations.index', + 'show' => 'api.depreciations.show', + 'store' => 'api.depreciations.store', + 'update' => 'api.depreciations.update', + 'destroy' => 'api.depreciations.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['depreciation' => 'depreciation_id'] + ] + ); // Depreciations resource + + + /*--- Fields API ---*/ + + Route::group(['prefix' => 'fields'], function () { + Route::post('fieldsets/{id}/order', + [ + 'as' => 'api.customfields.order', + 'uses' => 'CustomFieldsController@postReorder' + ] + ); + }); // Fields group + + + /*--- Groups API ---*/ + + Route::resource('groups', 'GroupsController', + [ + 'names' => + [ + 'index' => 'api.groups.index', + 'show' => 'api.groups.show', + 'store' => 'api.groups.store', + 'update' => 'api.groups.update', + 'destroy' => 'api.groups.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['group' => 'group_id'] + ] + ); // Groups resource + + + /*--- Hardware API ---*/ + + Route::resource('hardware', 'AssetsController', + [ + 'names' => + [ + 'index' => 'api.assets.index', + 'show' => 'api.assets.show', + 'store' => 'api.assets.store', + 'update' => 'api.assets.update', + 'destroy' => 'api.assets.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['asset' => 'asset_id'] + ] + ); // Hardware resource + + + /*--- Imports API ---*/ + + Route::resource('imports', 'ImportController', + [ + 'names' => + [ + 'index' => 'api.imports.index', + 'show' => 'api.imports.show', + 'store' => 'api.imports.store', + 'update' => 'api.imports.update', + 'destroy' => 'api.imports.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['import' => 'import_id'] + ] + ); // Imports resource + + Route::group(['prefix' => 'imports'], function () { + + Route::post('process/{import}', + [ + 'as' => 'api.imports.importFile', + 'uses'=> 'ImportController@process' + ] + ); + }); // Imports group + + + + + /*--- Licenses API ---*/ + + Route::resource('licenses', 'LicensesController', + [ + 'names' => + [ + 'index' => 'api.licenses.index', + 'show' => 'api.licenses.show', + 'store' => 'api.licenses.store', + 'update' => 'api.licenses.update', + 'destroy' => 'api.licenses.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['license' => 'license_id'] + ] + ); // Licenses resource + + + /*--- Locations API ---*/ + + Route::resource('locations', 'LocationsController', + [ + 'names' => + [ + 'index' => 'api.locations.index', + 'show' => 'api.locations.show', + 'store' => 'api.locations.store', + 'update' => 'api.locations.update', + 'destroy' => 'api.locations.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['location' => 'location_id'] + ] + ); // Locations resource + + Route::group(['prefix' => 'locations'], function () { + + Route::get('{location}/users', + [ + 'as'=>'api.locations.viewusers', + 'uses'=>'LocationsController@getDataViewUsers' + ] + ); + + Route::get('{location}/assets', + [ + 'as'=>'api.locations.viewassets', + 'uses'=>'LocationsController@getDataViewAssets' + ] + ); + + // Do we actually still need this, now that we have an API? + Route::get('{location}/check', + [ + 'as' => 'api.locations.check', + 'uses' => 'LocationsController@show' + ] + ); + }); // Locations group + + + /*--- Manufacturers API ---*/ + + Route::resource('manufacturers', 'ManufacturersController', + [ + 'names' => + [ + 'index' => 'api.manufacturers.index', + 'show' => 'api.manufacturers.show', + 'store' => 'api.manufacturers.store', + 'update' => 'api.manufacturers.update', + 'destroy' => 'api.manufacturers.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['manufacturer' => 'manufacturer_id'] + ] + ); // Manufacturers resource + + + /*--- Models API ---*/ + + Route::resource('models', 'AssetModelsController', + [ + 'names' => + [ + 'index' => 'api.models.index', + 'show' => 'api.models.show', + 'store' => 'api.models.store', + 'update' => 'api.models.update', + 'destroy' => 'api.models.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['model' => 'model_id'] + ] + ); // Models resource + + Route::group(['prefix' => 'models'], function () { + + Route::get('assets', + [ + 'as' => 'api.models.assets', + 'uses'=> 'AssetModelsController@assets' + ] + ); + }); // Models group + + + /*--- Settings API ---*/ + + Route::resource('settings', 'SettingsController', + [ + 'names' => + [ + 'index' => 'api.settings.index', + 'store' => 'api.settings.store', + 'show' => 'api.settings.show', + 'update' => 'api.settings.update' + ], + 'except' => ['create', 'edit', 'destroy'], + 'parameters' => ['setting' => 'setting_id'] + ] + ); // Settings resource + + + /*--- Status Labels API ---*/ + + Route::resource('statuslabels', 'StatuslabelsController', + [ + 'names' => + [ + 'index' => 'api.statuslabels.index', + 'store' => 'api.statuslabels.store', + 'show' => 'api.statuslabels.show', + 'update' => 'api.statuslabels.update', + 'destroy' => 'api.statuslabels.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['statuslabel' => 'statuslabel_id'] + ] ); + Route::group(['prefix' => 'statuslabels'], function () { - - - /*---Hardware API---*/ - - Route::match(['DELETE'], 'hardware/{id}', ['uses' => 'AssetsController@destroy','as' => 'api.assets.destroy']); - - - Route::resource('hardware', 'AssetsController', - ['names' => + Route::get('{statuslabel}/assetlist', [ - 'index' => 'api.assets.index', - 'create' => 'api.assets.create', - 'destroy' => 'api.assets.destroy' - ], - 'parameters' => - ['asset' => 'asset_id'] - ]); - - - /*---Locations API---*/ - Route::group(array('prefix'=>'locations'), function () { - - Route::get('{locationID}/users', array('as'=>'api.locations.viewusers', 'uses'=>'LocationsController@getDataViewUsers')); - Route::get('{locationID}/assets', array('as'=>'api.locations.viewassets', 'uses'=>'LocationsController@getDataViewAssets')); - - // Do we actually still need this, now that we have an API? - Route::get('{id}/check', - [ 'as' => 'api.locations.check', 'uses' => 'LocationsController@show' ]); - - }); - - - /*---Suppliers API---*/ - Route::group(array('prefix'=>'suppliers'), function () { - Route::get('list', array('as'=>'api.suppliers.list', 'uses'=>'SuppliersController@getDatatable')); - }); - - /*---Users API---*/ - Route::group([ 'prefix' => 'users' ], function () { - Route::post('/', [ 'as' => 'api.users.store', 'uses' => 'UsersController@store' ]); - Route::post('two_factor_reset', [ 'as' => 'api.users.two_factor_reset', 'uses' => 'UsersController@postTwoFactorReset' ]); - Route::get('list/{status?}', [ 'as' => 'api.users.list', 'uses' => 'UsersController@getDatatable' ]); - Route::get('{userId}/assets', [ 'as' => 'api.users.assetlist', 'uses' => 'UsersController@getAssetList' ]); - Route::post('{userId}/upload', [ 'as' => 'upload/user', 'uses' => 'UsersController@postUpload' ]); - }); - - - - - Route::group([ 'prefix' => 'fields' ], function () { - - Route::post( - 'fieldsets/{id}/order', - [ 'as' => 'api.customfields.order', 'uses' => 'CustomFieldsController@postReorder' ] + 'as' => 'api.statuslabels.assets', + 'uses' => 'StatuslabelsController@assets' + ] ); + Route::get('{statuslabel}/deployable', + [ + 'as' => 'api.statuslabels.deployable', + 'uses' => 'StatuslabelsController@checkIfDeployable' + ] + ); + + // Pie chart for dashboard + Route::get('assets', + [ + 'as' => 'api.statuslabels.assets.bytype', + 'uses' => 'StatuslabelsController@getAssetCountByStatuslabel' + ] + ); + }); // Status labels group + + + /*--- Suppliers API ---*/ + + Route::resource('suppliers', 'SuppliersController', + [ + 'names' => + [ + 'index' => 'api.suppliers.index', + 'show' => 'api.suppliers.show', + 'store' => 'api.suppliers.store', + 'update' => 'api.suppliers.update', + 'destroy' => 'api.suppliers.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['supplier' => 'supplier_id'] + ] + ); // Suppliers resource + + Route::group(['prefix' => 'suppliers'], function () { + + Route::get('list', + [ + 'as'=>'api.suppliers.list', + 'uses'=>'SuppliersController@getDatatable' + ] + ); + }); // Suppliers group + + + /*--- Users API ---*/ + + Route::resource('users', 'UsersController', + [ + 'names' => + [ + 'index' => 'api.users.index', + 'show' => 'api.users.show', + 'store' => 'api.users.store', + 'update' => 'api.users.update', + 'destroy' => 'api.users.destroy' + ], + 'except' => ['create', 'edit'], + 'parameters' => ['user' => 'user_id'] + ] + ); // Users resource + + Route::group([ 'prefix' => 'users' ], function () { + + Route::post('two_factor_reset', + [ + 'as' => 'api.users.two_factor_reset', + 'uses' => 'UsersController@postTwoFactorReset' + ] + ); + + Route::get('list/{status?}', + [ + 'as' => 'api.users.list', + 'uses' => 'UsersController@getDatatable' + ] + ); + + Route::get('{user}/assets', + [ + 'as' => 'api.users.assetlist', + 'uses' => 'UsersController@getAssetList' + ] + ); + + Route::post('{user}/upload', + [ + 'as' => 'api.users.uploads', + 'uses' => 'UsersController@postUpload' + ] + ); + }); // Users group + + + ### DEBUG ROUTES ### + + Route::group(['prefix' => 'me'], function () { + + if (env('APP_ENV') == 'production') { + abort(404); + } + + Route::get('/profile', function () { + return json_encode([ + 'name' => \Auth::user()->first_name . ' ' . \Auth::user()->last_name, + 'email' => \Auth::user()->email, + ]); + }); + + Route::get('/authenticated', function () { + return json_encode([ + 'authenticated' => \Auth::check() + ]); + }); + + Route::get('/permissions/{scope}/{action}', function ($scope, $action) { + return json_encode([ + 'permission' => $scope . '.' . $action, + 'authorized' => \Auth::user()->hasAccess($scope . '.' . $action), + ]); + }); + + Route::get('/permissions', function () { + return json_encode([ + 'permissions' => Auth::user()->permissions + ]); + }); + + }); - - - }); - - diff --git a/tests/_support/ApiTester.php b/tests/_support/ApiTester.php index c53fda8f8a..920d6d4078 100644 --- a/tests/_support/ApiTester.php +++ b/tests/_support/ApiTester.php @@ -24,23 +24,23 @@ class ApiTester extends \Codeception\Actor * Define custom actions here */ - public function getToken(\App\Models\User $user) - { - $client_repository = new \Laravel\Passport\ClientRepository(); - $client = $client_repository->createPersonalAccessClient($user->id, 'Codeception API Test Client', + public function getToken(\App\Models\User $user) + { + $client_repository = new \Laravel\Passport\ClientRepository(); + $client = $client_repository->createPersonalAccessClient($user->id, 'Codeception API Test Client', 'http://localhost/'); - \Illuminate\Support\Facades\DB::table('oauth_personal_access_clients')->insert([ + \Illuminate\Support\Facades\DB::table('oauth_personal_access_clients')->insert([ 'client_id' => $client->id, 'created_at' => new DateTime, 'updated_at' => new DateTime, - ]); + ]); - $user->permissions = json_encode(['superuser' => true]); - $user->save(); + $user->permissions = json_encode(['superuser' => true]); + $user->save(); - $token = $user->createToken('CodeceptionAPItestToken')->accessToken; + $token = $user->createToken('CodeceptionAPItestToken')->accessToken; - return $token; - } + return $token; + } } diff --git a/tests/api/ApiAssetsCest.php b/tests/api/ApiAssetsCest.php new file mode 100644 index 0000000000..285b7a63ac --- /dev/null +++ b/tests/api/ApiAssetsCest.php @@ -0,0 +1,210 @@ +faker = \Faker\Factory::create(); + $this->user = \App\Models\User::find(1); + + $I->amBearerAuthenticated($I->getToken($this->user)); + } + + /** @test */ + public function indexAssets(ApiTester $I) + { + $I->wantTo('Get a list of assets'); + + // setup + $assets = factory(\App\Models\Asset::class, 'asset', 10)->create([ + 'user_id' => $this->user->id, + ]); + + // call + $I->sendGET('/hardware'); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + $response = json_decode($I->grabResponse()); + + // sample verify + $asset = $assets->random(); + + $I->seeResponseContainsJson([ + 'id' => $asset->id, + 'name' => e($asset->name), + 'asset_tag' => $asset->asset_tag, + 'serial' => $asset->serial, + 'model' => [ + 'id' => $asset->model_id, + 'name' => e($asset->model->name), + ], + // TODO: model_label + 'last_checkout' => $asset->last_checkout, + // TODO: category [id, name] + // TODO: manufacturer [id, name] + 'notes' => $asset->notes, + 'expected_checkin' => $asset->expected_checkin, + 'order_number' => $asset->order_number, + 'company' => [ + 'id' => $asset->company->id, + 'name' => $asset->company->name, + ], + // TODO: location [id, name] + // TODO: rtd_location [id, name] + 'image' => $asset->image, + 'assigned_to' => $asset->assigned_to, + 'warranty' => $asset->warranty, + 'warranty_expires' => $asset->warranty_expires, + // TODO: created_at + 'purchase_date' => $asset->purchase_date->format('Y-m-d'), + 'purchase_cost' => \App\Helpers\Helper::formatCurrencyOutput($asset->purchase_cost), + // TODO: can_checkout + // TODO: available actions + ]); + } + + /** @test */ + public function createAsset(ApiTester $I, $scenario) + { + $I->wantTo('Create a new asset'); + + $temp_asset = factory(\App\Models\Asset::class, 'asset')->make(); + + // setup + $data = [ + 'asset_tag' => $temp_asset->tag, + 'assigned_to' => $temp_asset->assigned_to, + 'company_id' => $temp_asset->company->id, + 'image' => $temp_asset->image, + 'model_id' => $temp_asset->model_id, + 'name' => $temp_asset->name, + 'notes' => $temp_asset->notes, + 'purchase_cost' => $temp_asset->purchase_cost, + 'purchase_date' => $temp_asset->purchase_date, + 'rtd_location_id' => $temp_asset->rtd_location_id, + 'serial' => $temp_asset->serial, + 'status_id' => $temp_asset->status_id, + 'supplier_id' => $temp_asset->supplier_id, + 'warranty_months' => $temp_asset->warranty_months, + ]; + + $scenario->incomplete('When I POST to /hardware i am redirected to html login page 😰'); + // create + $I->sendPOST('/hardware', $data); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + } + + /** @test */ + public function updateAssetWithPatch(ApiTester $I, $scenario) + { + $I->wantTo('Update an asset with PATCH'); + + // create and store an asset + $asset = factory(\App\Models\Asset::class, 'asset')->create(); + $I->assertInstanceOf(\App\Models\Asset::class, $asset); + + // create a temporary asset to grab new data + $temp_asset = factory(\App\Models\Asset::class, 'asset')->make(); + $data = [ + 'asset_tag' => $temp_asset->tag, + 'assigned_to' => $temp_asset->assigned_to, + 'company_id' => $temp_asset->company->id, + 'image' => $temp_asset->image, + 'model_id' => $temp_asset->model_id, + 'name' => $temp_asset->name, + 'notes' => $temp_asset->notes, + 'purchase_cost' => $temp_asset->purchase_cost, + 'purchase_date' => $temp_asset->purchase_date->format('Y-m-d'), + 'rtd_location_id' => $temp_asset->rtd_location_id, + 'serial' => $temp_asset->serial, + 'status_id' => $temp_asset->status_id, + 'supplier_id' => $temp_asset->supplier_id, + 'warranty_months' => $temp_asset->warranty_months, + ]; + + // the asset name should be different + $I->assertNotEquals($asset->name, $data['name']); + + // update + $I->sendPATCH('/hardware/' . $asset->id, $data); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + $response = json_decode($I->grabResponse()); + $I->assertEquals('success', $response->status); + + // verify + $scenario->incomplete('[BadMethodCallException] Call to undefined method Illuminate\Database\Query\Builder::detail() 🤔'); + $I->sendGET('/hardware/' . $asset->id); + + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + $I->seeResponseContainsJson([ + 'name' => $data['name'], + 'id' => $asset->id, + ]); + } + + /** @test */ +/* public function updateAssetWithPut(ApiTester $I) + { + $I->wantTo('Update a asset with PUT'); + + // create + $asset = factory(\App\Models\Asset::class, 'asset')->create(); + $I->assertInstanceOf(\App\Models\Asset::class, $asset); + + $data = [ + 'name' => $this->faker->sentence(3), + ]; + + $I->assertNotEquals($asset->name, $data['name']); + + // update + $I->sendPUT('/hardware/' . $asset->id, $data); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + $response = json_decode($I->grabResponse()); + $I->assertEquals('success', $response->status); + + // verify + $I->sendGET('/hardware/' . $asset->id); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + $I->seeResponseContainsJson([ + 'name' => e($data['name']), + 'id' => e($asset->id), + 'qty' => e($asset->qty), + ]); + } + + /** @test */ +/* public function deleteAssetTest(ApiTester $I, $scenario) + { + $I->wantTo('Delete an asset'); + + // create + $asset = factory(\App\Models\Asset::class, 'asset')->create(); + $I->assertInstanceOf(\App\Models\Asset::class, $asset); + + // delete + $I->sendDELETE('/hardware/' . $asset->id); + $I->seeResponseIsJson(); + $I->seeResponseCodeIs(200); + + // verify, expect a 404 + $I->sendGET('/hardware/' . $asset->id); + $I->seeResponseCodeIs(404); + // $I->seeResponseIsJson(); // @todo: response is not JSON + $scenario->incomplete('404 response should be JSON, receiving HTML instead'); + } // */ +} diff --git a/tests/api/ApiComponentsAssetsCest.php b/tests/api/ApiComponentsAssetsCest.php index 48e0d3e604..3beea1675d 100644 --- a/tests/api/ApiComponentsAssetsCest.php +++ b/tests/api/ApiComponentsAssetsCest.php @@ -18,10 +18,11 @@ class ApiComponentsAssetsCest { $I->wantTo('Get a list of assets related to a component'); - // generate + // generate component $component = factory(\App\Models\Component::class, 'component') ->create(['user_id' => $this->user->id, 'qty' => 20]); + // generate assets and associate component $assets = factory(\App\Models\Asset::class, 'asset', 2) ->create(['user_id' => $this->user->id]) ->each(function ($asset) use ($component) { @@ -34,12 +35,14 @@ class ApiComponentsAssetsCest ]); }); + // verify $I->sendGET('/components/' . $component->id . '/assets/'); $I->seeResponseIsJson(); $I->seeResponseCodeIs(200); - $response = json_decode($I->grabResponse()); + $response = json_decode($I->grabResponse()); $I->assertEquals(2, $response->total); + $I->assertInstanceOf(\Illuminate\Database\Eloquent\Collection::class, $assets); $I->seeResponseContainsJson(['rows' => [ @@ -56,4 +59,22 @@ class ApiComponentsAssetsCest ] ]); } + + /** @test */ + public function expectEmptyResponseWithoutAssociatedAssets(ApiTester $I, $scenario) + { + $I->wantTo('See an empty response when there are no associated assets to a component'); + + $component = factory(\App\Models\Component::class, 'component') + ->create(['user_id' => $this->user->id, 'qty' => 20]); + + $I->sendGET('/components/' . $component->id . '/assets'); + $I->seeResponseCodeIs(200); + $I->seeResponseIsJson(); + + $response = json_decode($I->grabResponse()); + $I->assertEquals(0, $response->total); + $I->assertEquals([], $response->rows); + $I->seeResponseContainsJson(['total' => 0, 'rows' => []]); + } } diff --git a/tests/api/ApiComponentsCest.php b/tests/api/ApiComponentsCest.php index a0b311142d..c7327f5e56 100644 --- a/tests/api/ApiComponentsCest.php +++ b/tests/api/ApiComponentsCest.php @@ -21,7 +21,7 @@ class ApiComponentsCest $I->wantTo('Get a list of components'); // setup - $components = factory(\App\Models\Component::class, 'component', 10)->create(['user_id' => $this->user->id]); + $components = factory(\App\Models\Component::class, 'component', 10)->create(); // call $I->sendGET('/components'); @@ -34,6 +34,10 @@ class ApiComponentsCest 'name' => $component->name, 'qty' => $component->qty, ]); + + $I->seeResponseContainsJson([ + 'total' => \App\Models\Component::count(), + ]); } /** @test */ @@ -74,19 +78,19 @@ class ApiComponentsCest 'id' => $id, 'category' => [ 'id' => $data['category_id'], - 'name' => $category->name, + 'name' => e($category->name), ], 'company' => [ 'id' => $data['company_id'], - 'name' => $company->name, + 'name' => e($company->name), ], 'location' => [ 'id' => $data['location_id'], - 'name' => $location->name, + 'name' => e($location->name), ], 'name' => $data['name'], 'qty' => $data['qty'], - 'purchase_cost' => $data['purchase_cost'], + 'purchase_cost' => \App\Helpers\Helper::formatCurrencyOutput($data['purchase_cost']), 'purchase_date' => $data['purchase_date'], ]); } @@ -161,7 +165,7 @@ class ApiComponentsCest } /** @test */ - public function deleteComponentTest(ApiTester $I) + public function deleteComponentTest(ApiTester $I, $scenario) { $I->wantTo('Delete a component'); @@ -178,5 +182,6 @@ class ApiComponentsCest $I->sendGET('/components/' . $component->id); $I->seeResponseCodeIs(404); // $I->seeResponseIsJson(); // @todo: response is not JSON + $scenario->incomplete('404 response should be JSON, receiving HTML instead'); } }